@ascorbic/pds 0.2.0 → 0.2.2

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/cli.js CHANGED
@@ -339,6 +339,63 @@ const secretCommand = defineCommand({
339
339
  }
340
340
  });
341
341
 
342
+ //#endregion
343
+ //#region src/cli/utils/cli-helpers.ts
344
+ /**
345
+ * Shared CLI utilities for PDS commands
346
+ */
347
+ /**
348
+ * Prompt for text input, exiting on cancel
349
+ */
350
+ async function promptText(options) {
351
+ const result = await p.text(options);
352
+ if (p.isCancel(result)) {
353
+ p.cancel("Cancelled");
354
+ process.exit(0);
355
+ }
356
+ return result;
357
+ }
358
+ /**
359
+ * Prompt for confirmation, exiting on cancel
360
+ */
361
+ async function promptConfirm(options) {
362
+ const result = await p.confirm(options);
363
+ if (p.isCancel(result)) {
364
+ p.cancel("Cancelled");
365
+ process.exit(0);
366
+ }
367
+ return result;
368
+ }
369
+ /**
370
+ * Prompt for selection, exiting on cancel
371
+ */
372
+ async function promptSelect(options) {
373
+ const result = await p.select(options);
374
+ if (p.isCancel(result)) {
375
+ p.cancel("Cancelled");
376
+ process.exit(0);
377
+ }
378
+ return result;
379
+ }
380
+ /**
381
+ * Get target PDS URL based on mode
382
+ */
383
+ function getTargetUrl(isDev, pdsHostname) {
384
+ if (isDev) return `http://localhost:${process.env.PORT ? parseInt(process.env.PORT) ?? "5173" : "5173"}`;
385
+ if (!pdsHostname) throw new Error("PDS_HOSTNAME not configured in wrangler.jsonc");
386
+ return `https://${pdsHostname}`;
387
+ }
388
+ /**
389
+ * Extract domain from URL
390
+ */
391
+ function getDomain(url) {
392
+ try {
393
+ return new URL(url).hostname;
394
+ } catch {
395
+ return url;
396
+ }
397
+ }
398
+
342
399
  //#endregion
343
400
  //#region src/cli/utils/handle-resolver.ts
344
401
  /**
@@ -456,6 +513,22 @@ var DidResolver = class {
456
513
  function slugifyHandle(handle) {
457
514
  return handle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") + "-pds";
458
515
  }
516
+ const defaultWorkerName = "my-pds";
517
+ /**
518
+ * Prompt for worker name with validation
519
+ */
520
+ async function promptWorkerName(handle, currentWorkerName) {
521
+ const placeholder = currentWorkerName && currentWorkerName !== defaultWorkerName ? currentWorkerName : slugifyHandle(handle);
522
+ return promptText({
523
+ message: "Cloudflare Worker name:",
524
+ placeholder,
525
+ initialValue: placeholder,
526
+ validate: (v) => {
527
+ if (!v) return "Worker name is required";
528
+ if (!/^[a-z0-9-]+$/.test(v)) return "Worker name can only contain lowercase letters, numbers, and hyphens";
529
+ }
530
+ });
531
+ }
459
532
  /**
460
533
  * Run wrangler types to regenerate TypeScript types
461
534
  */
@@ -486,28 +559,24 @@ const initCommand = defineCommand({
486
559
  },
487
560
  args: { production: {
488
561
  type: "boolean",
489
- description: "Deploy secrets to Cloudflare (prompts to reuse .dev.vars values)",
562
+ description: "Deploy secrets to Cloudflare?",
490
563
  default: false
491
564
  } },
492
565
  async run({ args }) {
493
566
  p.intro("🦋 PDS Setup");
494
567
  const isProduction = args.production;
495
- if (isProduction) p.log.info("Production mode: secrets will be deployed via wrangler");
496
- else p.log.info("Let's set up your new home in the Atmosphere!");
568
+ if (isProduction) p.log.info("Production mode: secrets will be deployed to Cloudflare");
569
+ p.log.info("Let's set up your new home in the Atmosphere!");
497
570
  const wranglerVars = getVars();
498
571
  const devVars = readDevVars();
499
572
  const currentVars = {
500
573
  ...devVars,
501
574
  ...wranglerVars
502
575
  };
503
- const isMigrating = await p.confirm({
504
- message: "Are you migrating an existing Bluesky account? 🦋",
576
+ const isMigrating = await promptConfirm({
577
+ message: "Are you migrating an existing Bluesky/ATProto account?",
505
578
  initialValue: false
506
579
  });
507
- if (p.isCancel(isMigrating)) {
508
- p.cancel("Setup cancelled");
509
- process.exit(0);
510
- }
511
580
  let did;
512
581
  let handle;
513
582
  let hostname;
@@ -516,28 +585,24 @@ const initCommand = defineCommand({
516
585
  const currentWorkerName = getWorkerName();
517
586
  if (isMigrating) {
518
587
  p.log.info("Time to pack your bags! 🧳");
519
- p.log.info("Your account will be inactive until you've moved your data over.");
588
+ p.log.info("Your new account will be inactive until you're ready to go live.");
520
589
  let hostedDomains = [
521
590
  ".bsky.social",
522
591
  ".bsky.network",
523
592
  ".bsky.team"
524
593
  ];
525
- const isHostedHandle = (h) => hostedDomains.some((domain) => h.endsWith(domain));
594
+ const isHostedHandle = (h) => hostedDomains.some((domain) => h?.endsWith(domain));
526
595
  let resolvedDid = null;
527
596
  let existingHandle = null;
528
597
  let attempts = 0;
529
598
  const MAX_ATTEMPTS = 3;
530
599
  while (!resolvedDid && attempts < MAX_ATTEMPTS) {
531
600
  attempts++;
532
- const currentHandle = await p.text({
601
+ const currentHandle = await promptText({
533
602
  message: "Your current Bluesky/ATProto handle:",
534
603
  placeholder: "example.bsky.social",
535
604
  validate: (v) => !v ? "Handle is required" : void 0
536
605
  });
537
- if (p.isCancel(currentHandle)) {
538
- p.cancel("Cancelled");
539
- process.exit(0);
540
- }
541
606
  existingHandle = currentHandle;
542
607
  const spinner$1 = p.spinner();
543
608
  spinner$1.start("Finding you in the Atmosphere...");
@@ -545,7 +610,7 @@ const initCommand = defineCommand({
545
610
  if (!resolvedDid) {
546
611
  spinner$1.stop("Not found");
547
612
  p.log.error(`Failed to resolve handle "${currentHandle}"`);
548
- const action = await p.select({
613
+ if (await promptSelect({
549
614
  message: "What would you like to do?",
550
615
  options: [{
551
616
  value: "retry",
@@ -554,26 +619,14 @@ const initCommand = defineCommand({
554
619
  value: "manual",
555
620
  label: "Enter DID manually"
556
621
  }]
557
- });
558
- if (p.isCancel(action)) {
559
- p.cancel("Cancelled");
560
- process.exit(0);
561
- }
562
- if (action === "manual") {
563
- const manualDid = await p.text({
564
- message: "Enter your DID:",
565
- placeholder: "did:plc:...",
566
- validate: (v) => {
567
- if (!v) return "DID is required";
568
- if (!v.startsWith("did:")) return "DID must start with did:";
569
- }
570
- });
571
- if (p.isCancel(manualDid)) {
572
- p.cancel("Cancelled");
573
- process.exit(0);
622
+ }) === "manual") resolvedDid = await promptText({
623
+ message: "Enter your DID:",
624
+ placeholder: "did:plc:...",
625
+ validate: (v) => {
626
+ if (!v) return "DID is required";
627
+ if (!v.startsWith("did:")) return "DID must start with did:";
574
628
  }
575
- resolvedDid = manualDid;
576
- }
629
+ });
577
630
  } else {
578
631
  try {
579
632
  const pdsService = (await new DidResolver().resolve(resolvedDid))?.service?.find((s) => s.type === "AtprotoPersonalDataServer" || s.id === "#atproto_pds");
@@ -587,7 +640,7 @@ const initCommand = defineCommand({
587
640
  } catch {}
588
641
  spinner$1.stop(`Found you! ${resolvedDid}`);
589
642
  if (isHostedHandle(existingHandle)) {
590
- const theirDomain = hostedDomains.find((d) => existingHandle.endsWith(d));
643
+ const theirDomain = hostedDomains.find((d) => existingHandle?.endsWith(d));
591
644
  const domainExample = theirDomain ? `*${theirDomain}` : "*.bsky.social";
592
645
  p.log.warn(`You'll need a custom domain for your new handle (not ${domainExample}). You can set this up after transferring your data.`);
593
646
  }
@@ -604,95 +657,48 @@ const initCommand = defineCommand({
604
657
  }
605
658
  }
606
659
  did = resolvedDid;
607
- const defaultHandle = existingHandle && !isHostedHandle(existingHandle) ? existingHandle : currentVars.HANDLE || "";
608
- handle = await p.text({
660
+ handle = await promptText({
609
661
  message: "New account handle (must be a domain you control):",
610
662
  placeholder: "example.com",
611
- initialValue: defaultHandle,
663
+ initialValue: existingHandle && !isHostedHandle(existingHandle) ? existingHandle : currentVars.HANDLE || "",
612
664
  validate: (v) => {
613
665
  if (!v) return "Handle is required";
614
666
  if (isHostedHandle(v)) return "You need a custom domain - hosted handles like *.bsky.social won't work";
615
667
  }
616
668
  });
617
- if (p.isCancel(handle)) {
618
- p.cancel("Cancelled");
619
- process.exit(0);
620
- }
621
- hostname = await p.text({
669
+ hostname = await promptText({
622
670
  message: "Domain where you'll deploy your PDS:",
623
671
  placeholder: handle,
624
672
  initialValue: currentVars.PDS_HOSTNAME || handle,
625
673
  validate: (v) => !v ? "Hostname is required" : void 0
626
674
  });
627
- if (p.isCancel(hostname)) {
628
- p.cancel("Cancelled");
629
- process.exit(0);
630
- }
631
- const defaultWorkerName = currentWorkerName || slugifyHandle(handle);
632
- workerName = await p.text({
633
- message: "Cloudflare Worker name:",
634
- placeholder: defaultWorkerName,
635
- initialValue: defaultWorkerName,
636
- validate: (v) => {
637
- if (!v) return "Worker name is required";
638
- if (!/^[a-z0-9-]+$/.test(v)) return "Worker name can only contain lowercase letters, numbers, and hyphens";
639
- }
640
- });
641
- if (p.isCancel(workerName)) {
642
- p.cancel("Cancelled");
643
- process.exit(0);
644
- }
675
+ workerName = await promptWorkerName(handle, currentWorkerName);
645
676
  initialActive = "false";
646
677
  } else {
647
678
  p.log.info("A fresh start in the Atmosphere! ✨");
648
- hostname = await p.text({
679
+ hostname = await promptText({
649
680
  message: "Domain where you'll deploy your PDS:",
650
681
  placeholder: "pds.example.com",
651
682
  initialValue: currentVars.PDS_HOSTNAME || "",
652
683
  validate: (v) => !v ? "Hostname is required" : void 0
653
684
  });
654
- if (p.isCancel(hostname)) {
655
- p.cancel("Cancelled");
656
- process.exit(0);
657
- }
658
- handle = await p.text({
685
+ handle = await promptText({
659
686
  message: "Account handle:",
660
687
  placeholder: hostname,
661
688
  initialValue: currentVars.HANDLE || hostname,
662
689
  validate: (v) => !v ? "Handle is required" : void 0
663
690
  });
664
- if (p.isCancel(handle)) {
665
- p.cancel("Cancelled");
666
- process.exit(0);
667
- }
668
691
  const didDefault = "did:web:" + hostname;
669
- did = await p.text({
692
+ did = await promptText({
670
693
  message: "Account DID:",
671
694
  placeholder: didDefault,
672
695
  initialValue: currentVars.DID || didDefault,
673
696
  validate: (v) => {
674
697
  if (!v) return "DID is required";
675
- if (!v.startsWith("did:")) return "DID must start with did:";
698
+ if (!v.startsWith("did:")) return "DID must start with 'did:'";
676
699
  }
677
700
  });
678
- if (p.isCancel(did)) {
679
- p.cancel("Cancelled");
680
- process.exit(0);
681
- }
682
- const defaultWorkerName = currentWorkerName || slugifyHandle(handle);
683
- workerName = await p.text({
684
- message: "Cloudflare Worker name:",
685
- placeholder: defaultWorkerName,
686
- initialValue: defaultWorkerName,
687
- validate: (v) => {
688
- if (!v) return "Worker name is required";
689
- if (!/^[a-z0-9-]+$/.test(v)) return "Worker name can only contain lowercase letters, numbers, and hyphens";
690
- }
691
- });
692
- if (p.isCancel(workerName)) {
693
- p.cancel("Cancelled");
694
- process.exit(0);
695
- }
701
+ workerName = await promptWorkerName(handle, currentWorkerName);
696
702
  initialActive = "true";
697
703
  if (handle === hostname) p.note([
698
704
  "Your handle matches your PDS hostname, so your PDS will",
@@ -719,55 +725,32 @@ const initCommand = defineCommand({
719
725
  ].join("\n"), "Identity Setup 🪪");
720
726
  }
721
727
  const spinner = p.spinner();
722
- let authToken;
723
- let signingKey;
724
- let signingKeyPublic;
725
- let jwtSecret;
726
- let passwordHash;
727
- if (isProduction) {
728
- authToken = await getOrGenerateSecret("AUTH_TOKEN", devVars, async () => {
729
- spinner.start("Generating auth token...");
730
- const token = generateAuthToken();
731
- spinner.stop("Auth token generated");
732
- return token;
733
- });
734
- signingKey = await getOrGenerateSecret("SIGNING_KEY", devVars, async () => {
735
- spinner.start("Generating signing keypair...");
736
- const { privateKey } = await generateSigningKeypair();
737
- spinner.stop("Signing keypair generated");
738
- return privateKey;
739
- });
740
- signingKeyPublic = await derivePublicKey(signingKey);
741
- jwtSecret = await getOrGenerateSecret("JWT_SECRET", devVars, async () => {
742
- spinner.start("Generating JWT secret...");
743
- const secret = generateJwtSecret();
744
- spinner.stop("JWT secret generated");
745
- return secret;
746
- });
747
- passwordHash = await getOrGenerateSecret("PASSWORD_HASH", devVars, async () => {
748
- const password = await promptPassword(handle);
749
- spinner.start("Hashing password...");
750
- const hash = await hashPassword(password);
751
- spinner.stop("Password hashed");
752
- return hash;
753
- });
754
- } else {
755
- const password = await promptPassword(handle);
756
- spinner.start("Hashing password...");
757
- passwordHash = await hashPassword(password);
758
- spinner.stop("Password hashed");
759
- spinner.start("Generating JWT secret...");
760
- jwtSecret = generateJwtSecret();
761
- spinner.stop("JWT secret generated");
728
+ const authToken = await getOrGenerateSecret("AUTH_TOKEN", devVars, async () => {
762
729
  spinner.start("Generating auth token...");
763
- authToken = generateAuthToken();
730
+ const token = generateAuthToken();
764
731
  spinner.stop("Auth token generated");
732
+ return token;
733
+ });
734
+ const signingKey = await getOrGenerateSecret("SIGNING_KEY", devVars, async () => {
765
735
  spinner.start("Generating signing keypair...");
766
- const keypair = await generateSigningKeypair();
767
- signingKey = keypair.privateKey;
768
- signingKeyPublic = keypair.publicKey;
736
+ const { privateKey } = await generateSigningKeypair();
769
737
  spinner.stop("Signing keypair generated");
770
- }
738
+ return privateKey;
739
+ });
740
+ const signingKeyPublic = await derivePublicKey(signingKey);
741
+ const jwtSecret = await getOrGenerateSecret("JWT_SECRET", devVars, async () => {
742
+ spinner.start("Generating JWT secret...");
743
+ const secret = generateJwtSecret();
744
+ spinner.stop("JWT secret generated");
745
+ return secret;
746
+ });
747
+ const passwordHash = await getOrGenerateSecret("PASSWORD_HASH", devVars, async () => {
748
+ const password = await promptPassword(handle);
749
+ spinner.start("Hashing password...");
750
+ const hash = await hashPassword(password);
751
+ spinner.stop("Password hashed");
752
+ return hash;
753
+ });
771
754
  spinner.start("Updating wrangler.jsonc...");
772
755
  setWorkerName(workerName);
773
756
  setVars({
@@ -844,15 +827,10 @@ const initCommand = defineCommand({
844
827
  */
845
828
  async function getOrGenerateSecret(name, devVars, generate) {
846
829
  if (devVars[name]) {
847
- const useExisting = await p.confirm({
830
+ if (await p.confirm({
848
831
  message: `Use ${name} from .dev.vars?`,
849
832
  initialValue: true
850
- });
851
- if (p.isCancel(useExisting)) {
852
- p.cancel("Cancelled");
853
- process.exit(0);
854
- }
855
- if (useExisting) return devVars[name];
833
+ }) === true) return devVars[name];
856
834
  }
857
835
  return generate();
858
836
  }
@@ -981,6 +959,21 @@ var PDSClient = class {
981
959
  return this.xrpc("GET", "com.atproto.sync.listBlobs", { params });
982
960
  }
983
961
  /**
962
+ * Get user preferences
963
+ */
964
+ async getPreferences() {
965
+ return (await this.xrpc("GET", "app.bsky.actor.getPreferences", { auth: true })).preferences;
966
+ }
967
+ /**
968
+ * Update user preferences
969
+ */
970
+ async putPreferences(preferences) {
971
+ await this.xrpc("POST", "app.bsky.actor.putPreferences", {
972
+ body: { preferences },
973
+ auth: true
974
+ });
975
+ }
976
+ /**
984
977
  * Get account status including migration progress
985
978
  */
986
979
  async getAccountStatus() {
@@ -1048,31 +1041,6 @@ var PDSClient = class {
1048
1041
  }
1049
1042
  };
1050
1043
 
1051
- //#endregion
1052
- //#region src/cli/utils/cli-helpers.ts
1053
- /**
1054
- * Shared CLI utilities for PDS commands
1055
- */
1056
- /**
1057
- * Get target PDS URL based on mode
1058
- */
1059
- function getTargetUrl(isDev, pdsHostname) {
1060
- const LOCAL_PDS_URL = "http://localhost:5173";
1061
- if (isDev) return LOCAL_PDS_URL;
1062
- if (!pdsHostname) throw new Error("PDS_HOSTNAME not configured in wrangler.jsonc");
1063
- return `https://${pdsHostname}`;
1064
- }
1065
- /**
1066
- * Extract domain from URL
1067
- */
1068
- function getDomain(url) {
1069
- try {
1070
- return new URL(url).hostname;
1071
- } catch {
1072
- return url;
1073
- }
1074
- }
1075
-
1076
1044
  //#endregion
1077
1045
  //#region src/cli/commands/migrate.ts
1078
1046
  function detectPackageManager() {
@@ -1083,11 +1051,10 @@ function detectPackageManager() {
1083
1051
  return "npm";
1084
1052
  }
1085
1053
  const brightNote$1 = (lines) => lines.map((l) => `\x1b[0m${l}`).join("\n");
1086
- const bold$1 = (text) => pc.bold(text);
1087
1054
  /**
1088
1055
  * Format number with commas
1089
1056
  */
1090
- function formatNumber(n) {
1057
+ function num(n) {
1091
1058
  return n.toLocaleString();
1092
1059
  }
1093
1060
  /**
@@ -1116,7 +1083,8 @@ const migrateCommand = defineCommand({
1116
1083
  }
1117
1084
  },
1118
1085
  async run({ args }) {
1119
- const pm = detectPackageManager();
1086
+ const packageManager = detectPackageManager();
1087
+ const pm = packageManager === "npm" ? "npm run" : packageManager;
1120
1088
  const isDev = args.dev;
1121
1089
  const vars = getVars();
1122
1090
  let targetUrl;
@@ -1183,6 +1151,7 @@ const migrateCommand = defineCommand({
1183
1151
  const sourceDomain = getDomain(sourcePdsUrl);
1184
1152
  spinner.stop(`Found your account at ${sourceDomain}`);
1185
1153
  spinner.start("Checking account status...");
1154
+ const pdsDisplayName = sourceDomain.endsWith(".bsky.network") ? "bsky.social" : sourceDomain;
1186
1155
  let status;
1187
1156
  try {
1188
1157
  status = await targetClient.getAccountStatus();
@@ -1197,7 +1166,7 @@ const migrateCommand = defineCommand({
1197
1166
  if (status.active) {
1198
1167
  p.log.error("Cannot reset: account is active");
1199
1168
  p.log.info("The --clean flag only works on deactivated accounts.");
1200
- p.log.info("Your account is already live in the Atmosphere.");
1169
+ p.log.info("Your account is already live");
1201
1170
  p.log.info("");
1202
1171
  p.log.info("If you need to re-import, first deactivate:");
1203
1172
  p.log.info(" pnpm pds deactivate");
@@ -1205,13 +1174,13 @@ const migrateCommand = defineCommand({
1205
1174
  process.exit(1);
1206
1175
  }
1207
1176
  p.note(brightNote$1([
1208
- bold$1("This will permanently delete from your new PDS:"),
1177
+ pc.bold("This will permanently delete from your new PDS:"),
1209
1178
  "",
1210
- ` • ${formatNumber(status.repoBlocks)} repository blocks`,
1211
- ` • ${formatNumber(status.importedBlobs)} imported images`,
1179
+ ` • ${num(status.repoBlocks)} repository blocks`,
1180
+ ` • ${num(status.importedBlobs)} imported images`,
1212
1181
  " • All blob tracking data",
1213
1182
  "",
1214
- bold$1(`Your data on ${sourceDomain} is NOT affected.`),
1183
+ pc.bold(`Your data on ${pdsDisplayName} is NOT affected.`),
1215
1184
  "You'll need to re-import everything."
1216
1185
  ]), "⚠️ Reset Migration Data");
1217
1186
  const confirmReset = await p.confirm({
@@ -1225,7 +1194,7 @@ const migrateCommand = defineCommand({
1225
1194
  spinner.start("Resetting migration state...");
1226
1195
  try {
1227
1196
  const result = await targetClient.resetMigration();
1228
- spinner.stop(`Deleted ${formatNumber(result.blocksDeleted)} blocks, ${formatNumber(result.blobsCleared)} blobs`);
1197
+ spinner.stop(`Deleted ${num(result.blocksDeleted)} blocks, ${num(result.blobsCleared)} blobs`);
1229
1198
  } catch (err) {
1230
1199
  spinner.stop("Reset failed");
1231
1200
  p.log.error(err instanceof Error ? err.message : "Could not reset migration");
@@ -1236,12 +1205,12 @@ const migrateCommand = defineCommand({
1236
1205
  status = await targetClient.getAccountStatus();
1237
1206
  }
1238
1207
  if (status.active) {
1239
- p.log.warn("Your account is already active in the Atmosphere!");
1208
+ p.log.warn(`Your account is already active at ${targetDomain}!`);
1240
1209
  p.log.info("No migration needed - your PDS is live.");
1241
- p.outro("All good! 🦋");
1210
+ p.outro("All good!");
1242
1211
  return;
1243
1212
  }
1244
- spinner.start(`Fetching your account details from ${sourceDomain}...`);
1213
+ spinner.start(`Fetching your account details from ${pdsDisplayName}...`);
1245
1214
  const sourceClient = new PDSClient(sourcePdsUrl);
1246
1215
  try {
1247
1216
  await sourceClient.describeRepo(did);
@@ -1262,10 +1231,10 @@ const migrateCommand = defineCommand({
1262
1231
  `@${handle} (${did.slice(0, 20)}...)`,
1263
1232
  "",
1264
1233
  "✓ Repository imported",
1265
- `◐ Images: ${formatNumber(status.importedBlobs)}/${formatNumber(status.expectedBlobs)} transferred`
1234
+ `◐ Media: ${num(status.importedBlobs)}/${num(status.expectedBlobs)} images and videos transferred`
1266
1235
  ].join("\n"), "Migration Progress");
1267
1236
  const continueTransfer = await p.confirm({
1268
- message: "Continue transferring images?",
1237
+ message: "Continue transferring images and video?",
1269
1238
  initialValue: true
1270
1239
  });
1271
1240
  if (p.isCancel(continueTransfer) || !continueTransfer) {
@@ -1274,14 +1243,14 @@ const migrateCommand = defineCommand({
1274
1243
  }
1275
1244
  } else if (needsRepoImport) {
1276
1245
  p.log.info("Time to pack your bags!");
1277
- p.log.info("Let's move your Bluesky account to its new home in the Atmosphere.");
1246
+ p.log.info("Let's clone your account to its new home in the Atmosphere.");
1278
1247
  const statsLines = profileStats ? [
1279
- ` 📝 ${formatNumber(profileStats.postsCount)} posts`,
1280
- ` 👥 ${formatNumber(profileStats.followsCount)} follows`,
1281
- ` ...plus all your images, likes, and blocks`
1282
- ] : [` 📝 Posts, follows, images, likes, and blocks`];
1248
+ ` 📝 ${num(profileStats.postsCount)} posts`,
1249
+ ` 👥 ${num(profileStats.followsCount)} follows`,
1250
+ ` ...plus all your images, likes and preferences`
1251
+ ] : [` 📝 Posts, follows, images, likes and preferences`];
1283
1252
  p.note(brightNote$1([
1284
- bold$1(`@${handle}`) + ` (${did.slice(0, 20)}...)`,
1253
+ pc.bold(`@${handle}`) + ` (${did.slice(0, 20)}...)`,
1285
1254
  "",
1286
1255
  `Currently at: ${sourceDomain}`,
1287
1256
  `Moving to: ${targetDomain}`,
@@ -1289,9 +1258,9 @@ const migrateCommand = defineCommand({
1289
1258
  "What you're bringing:",
1290
1259
  ...statsLines
1291
1260
  ]), "Your Bluesky Account 🦋");
1292
- p.log.info("This will copy your data - nothing is changed or deleted on Bluesky.");
1261
+ p.log.info(`This will copy your data - nothing is changed or deleted on your current PDS.`);
1293
1262
  const proceed = await p.confirm({
1294
- message: "Ready to start packing?",
1263
+ message: "Ready to start moving?",
1295
1264
  initialValue: true
1296
1265
  });
1297
1266
  if (p.isCancel(proceed) || !proceed) {
@@ -1299,19 +1268,17 @@ const migrateCommand = defineCommand({
1299
1268
  process.exit(0);
1300
1269
  }
1301
1270
  } else {
1302
- p.log.success("All packed and moved! 🦋");
1303
- showNextSteps(pm, sourceDomain);
1271
+ p.log.success("Everything looks good!");
1272
+ showNextSteps(pm, pdsDisplayName);
1304
1273
  p.outro("Welcome to your new home in the Atmosphere! 🦋");
1305
1274
  return;
1306
1275
  }
1307
- const isBlueskyPds = sourceDomain.endsWith(".bsky.network");
1308
- const passwordPrompt = isBlueskyPds ? "Your current Bluesky password:" : `Your ${sourceDomain} password:`;
1309
- const password = await p.password({ message: passwordPrompt });
1276
+ const password = await p.password({ message: `Your password for ${pdsDisplayName}:` });
1310
1277
  if (p.isCancel(password)) {
1311
1278
  p.cancel("Migration cancelled.");
1312
1279
  process.exit(0);
1313
1280
  }
1314
- spinner.start(`Logging in to ${isBlueskyPds ? "Bluesky" : sourceDomain}...`);
1281
+ spinner.start(`Logging in to ${pdsDisplayName}...`);
1315
1282
  try {
1316
1283
  const session = await sourceClient.createSession(did, password);
1317
1284
  sourceClient.setAuthToken(session.accessJwt);
@@ -1324,7 +1291,7 @@ const migrateCommand = defineCommand({
1324
1291
  process.exit(1);
1325
1292
  }
1326
1293
  if (needsRepoImport) {
1327
- spinner.start("Packing your repository...");
1294
+ spinner.start(`Exporting your repository from ${pdsDisplayName}...`);
1328
1295
  let carBytes;
1329
1296
  try {
1330
1297
  carBytes = await sourceClient.getRepo(did);
@@ -1335,7 +1302,7 @@ const migrateCommand = defineCommand({
1335
1302
  p.outro("Migration cancelled.");
1336
1303
  process.exit(1);
1337
1304
  }
1338
- spinner.start(`Unpacking at ${targetDomain}...`);
1305
+ spinner.start(`Importing to ${targetDomain}...`);
1339
1306
  try {
1340
1307
  await targetClient.importRepo(carBytes);
1341
1308
  spinner.stop("Repository imported");
@@ -1347,6 +1314,16 @@ const migrateCommand = defineCommand({
1347
1314
  }
1348
1315
  status = await targetClient.getAccountStatus();
1349
1316
  }
1317
+ spinner.start("Migrating your preferences...");
1318
+ try {
1319
+ const preferences = await sourceClient.getPreferences();
1320
+ if (preferences.length > 0) {
1321
+ await targetClient.putPreferences(preferences);
1322
+ spinner.stop(`Migrated ${preferences.length} preference${preferences.length === 1 ? "" : "s"}`);
1323
+ } else spinner.stop("No preferences to migrate");
1324
+ } catch (err) {
1325
+ spinner.stop("Skipped preferences (not available)");
1326
+ }
1350
1327
  if (status.expectedBlobs - status.importedBlobs > 0) {
1351
1328
  let synced = 0;
1352
1329
  let totalBlobs = 0;
@@ -1366,7 +1343,7 @@ const migrateCommand = defineCommand({
1366
1343
  totalBlobs += page.blobs.length;
1367
1344
  countCursor = page.cursor;
1368
1345
  } while (countCursor);
1369
- spinner.message(`Transferring images ${progressBar(0, totalBlobs)}`);
1346
+ spinner.message(`Transferring media ${progressBar(0, totalBlobs)}`);
1370
1347
  do {
1371
1348
  const page = await targetClient.listMissingBlobs(100, cursor);
1372
1349
  cursor = page.cursor;
@@ -1374,40 +1351,40 @@ const migrateCommand = defineCommand({
1374
1351
  const { bytes, mimeType } = await sourceClient.getBlob(did, blob.cid);
1375
1352
  await targetClient.uploadBlob(bytes, mimeType);
1376
1353
  synced++;
1377
- spinner.message(`Transferring images ${progressBar(synced, totalBlobs)}`);
1354
+ spinner.message(`Transferring media ${progressBar(synced, totalBlobs)}`);
1378
1355
  } catch (err) {
1379
1356
  synced++;
1380
1357
  failedBlobs.push(blob.cid);
1381
- spinner.message(`Transferring images ${progressBar(synced, totalBlobs)}`);
1358
+ spinner.message(`Transferring media ${progressBar(synced, totalBlobs)}`);
1382
1359
  }
1383
1360
  } while (cursor);
1384
1361
  if (failedBlobs.length > 0) {
1385
- spinner.stop(`Transferred ${formatNumber(synced - failedBlobs.length)} images (${failedBlobs.length} failed)`);
1362
+ spinner.stop(`Transferred ${num(synced - failedBlobs.length)} images and videos (${failedBlobs.length} failed)`);
1386
1363
  p.log.warn(`Run 'pds migrate' again to retry failed transfers.`);
1387
- } else spinner.stop(`Transferred ${formatNumber(synced)} images`);
1364
+ } else spinner.stop(`Transferred ${num(synced)} images and videos`);
1388
1365
  }
1389
1366
  spinner.start("Verifying migration...");
1390
1367
  const finalStatus = await targetClient.getAccountStatus();
1391
1368
  spinner.stop("Verification complete");
1392
- if (finalStatus.importedBlobs >= finalStatus.expectedBlobs) p.log.success("All packed and moved! 🦋");
1369
+ if (finalStatus.importedBlobs >= finalStatus.expectedBlobs) p.log.success("All packed and moved!");
1393
1370
  else {
1394
1371
  p.log.warn(`Migration partially complete. ${finalStatus.expectedBlobs - finalStatus.importedBlobs} images remaining.`);
1395
1372
  p.log.info("Run 'pds migrate' again to continue.");
1396
1373
  }
1397
- showNextSteps(pm, sourceDomain);
1374
+ showNextSteps(pm, pdsDisplayName);
1398
1375
  p.outro("Welcome to your new home in the Atmosphere! 🦋");
1399
1376
  }
1400
1377
  });
1401
1378
  function showNextSteps(pm, sourceDomain) {
1402
1379
  p.note(brightNote$1([
1403
- bold$1("Your data is safe in your new PDS."),
1404
- "Two more steps to go live in the Atmosphere:",
1380
+ pc.bold("Your data is safe in your new PDS."),
1381
+ "Two more steps to go live:",
1405
1382
  "",
1406
- bold$1("1. Update your identity"),
1383
+ pc.bold("1. Update your identity"),
1407
1384
  " Tell the network where you live now.",
1408
1385
  ` (Requires email verification from ${sourceDomain})`,
1409
1386
  "",
1410
- bold$1("2. Flip the switch"),
1387
+ pc.bold("2. Flip the switch"),
1411
1388
  ` ${pm} pds activate`,
1412
1389
  "",
1413
1390
  "Docs: https://atproto.com/guides/account-migration"