crosssync 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -77,9 +77,25 @@ crosssync sync --bookmarks --force # 浏览器运行中也强制写回
77
77
  crosssync sync --strategy export # 完全只读
78
78
  crosssync sync --browser edge # 只处理 Edge
79
79
  crosssync sync --profile all # 处理所有 Chromium profile
80
+ crosssync sync --profile "Default,Profile 11" # 同时处理多个 profile
80
81
  crosssync sync --json # 机器可读报告
81
82
  ```
82
83
 
84
+ 如果配置的 profile(默认 `Default`)在某个浏览器中不存在,交互式终端会列出可用的
85
+ profile 让你按序号选择(支持多选),并自动保存选择。列表会附带 `Local State` 中记录的
86
+ 本地名称和已登录账号(如 `Profile 11 Roe <roe@example.com>`),便于区分 profile。
87
+
88
+ ### `crosssync profiles`
89
+
90
+ ```bash
91
+ crosssync profiles # 列出可用 profile 和当前选择
92
+ crosssync profiles --set "Default,Profile 11,Profile 12"
93
+ crosssync profiles --add "Profile 9"
94
+ crosssync profiles --remove "Profile 11"
95
+ crosssync profiles --all # 选择所有 profile
96
+ crosssync profiles --reset # 恢复为 Default
97
+ ```
98
+
83
99
  ### `crosssync backup`
84
100
 
85
101
  ```bash
package/dist/index.js CHANGED
@@ -3036,7 +3036,7 @@ var require_constants = __commonJS({
3036
3036
  "../shared/dist/constants.js"(exports2) {
3037
3037
  "use strict";
3038
3038
  Object.defineProperty(exports2, "__esModule", { value: true });
3039
- exports2.DEFAULT_CRON_SCHEDULE = exports2.DEFAULT_EXPORT_RETENTION = exports2.CONFIG_VERSION = exports2.DEFAULT_LOG_LEVEL = exports2.DEFAULT_PROFILE = exports2.PROFILE_NAME_PATTERN = exports2.HISTORY_FILE_NAME = exports2.BOOKMARKS_FILE_NAME = exports2.LOG_FILE_NAME = exports2.CONFIG_FILE_NAME = exports2.LOG_DIR_NAME = exports2.EXPORT_DIR_NAME = exports2.BACKUP_DIR_NAME = exports2.CROSSSYNC_DIR_NAME = exports2.CHROMIUM_EPOCH_OFFSET_MS = exports2.BROWSERS = exports2.LOG_LEVELS = exports2.SHARED_VERSION = exports2.SHARED_PACKAGE = void 0;
3039
+ exports2.DEFAULT_CRON_SCHEDULE = exports2.DEFAULT_EXPORT_RETENTION = exports2.CONFIG_VERSION = exports2.DEFAULT_LOG_LEVEL = exports2.DEFAULT_PROFILE = exports2.PROFILE_NAME_PATTERN = exports2.LOCAL_STATE_FILE_NAME = exports2.HISTORY_FILE_NAME = exports2.BOOKMARKS_FILE_NAME = exports2.LOG_FILE_NAME = exports2.CONFIG_FILE_NAME = exports2.LOG_DIR_NAME = exports2.EXPORT_DIR_NAME = exports2.BACKUP_DIR_NAME = exports2.CROSSSYNC_DIR_NAME = exports2.CHROMIUM_EPOCH_OFFSET_MS = exports2.BROWSERS = exports2.LOG_LEVELS = exports2.SHARED_VERSION = exports2.SHARED_PACKAGE = void 0;
3040
3040
  exports2.SHARED_PACKAGE = "@crosssync/shared";
3041
3041
  exports2.SHARED_VERSION = "0.1.0";
3042
3042
  exports2.LOG_LEVELS = ["debug", "info", "warn", "error"];
@@ -3050,6 +3050,7 @@ var require_constants = __commonJS({
3050
3050
  exports2.LOG_FILE_NAME = "crosssync.log";
3051
3051
  exports2.BOOKMARKS_FILE_NAME = "Bookmarks";
3052
3052
  exports2.HISTORY_FILE_NAME = "History";
3053
+ exports2.LOCAL_STATE_FILE_NAME = "Local State";
3053
3054
  exports2.PROFILE_NAME_PATTERN = /^(Default|Profile \d+)$/;
3054
3055
  exports2.DEFAULT_PROFILE = "Default";
3055
3056
  exports2.DEFAULT_LOG_LEVEL = "info";
@@ -3126,12 +3127,36 @@ var require_config = __commonJS({
3126
3127
  Object.defineProperty(exports2, "__esModule", { value: true });
3127
3128
  exports2.SYNC_STRATEGIES = void 0;
3128
3129
  exports2.resolveExportRetention = resolveExportRetention3;
3130
+ exports2.parseProfileList = parseProfileList2;
3131
+ exports2.addProfilesToSelection = addProfilesToSelection3;
3129
3132
  exports2.createDefaultConfig = createDefaultConfig3;
3130
3133
  var constants_1 = require_constants();
3131
3134
  exports2.SYNC_STRATEGIES = ["merge", "export"];
3132
3135
  function resolveExportRetention3(config) {
3133
3136
  return config.exports?.keep ?? constants_1.DEFAULT_EXPORT_RETENTION;
3134
3137
  }
3138
+ function parseProfileList2(value) {
3139
+ const names = [];
3140
+ for (const part of value.split(",")) {
3141
+ const name = part.trim();
3142
+ if (name.length > 0 && !names.includes(name)) {
3143
+ names.push(name);
3144
+ }
3145
+ }
3146
+ return names;
3147
+ }
3148
+ function addProfilesToSelection3(current, additions) {
3149
+ if (current.trim() === "all") {
3150
+ return "all";
3151
+ }
3152
+ const names = parseProfileList2(current);
3153
+ for (const name of additions) {
3154
+ if (name.trim().length > 0 && !names.includes(name.trim())) {
3155
+ names.push(name.trim());
3156
+ }
3157
+ }
3158
+ return names.join(",");
3159
+ }
3135
3160
  function createDefaultConfig3(options) {
3136
3161
  return {
3137
3162
  version: constants_1.CONFIG_VERSION,
@@ -3182,7 +3207,7 @@ var require_utils = __commonJS({
3182
3207
  exports2.toChromiumTime = toChromiumTime;
3183
3208
  exports2.resolveHomeDir = resolveHomeDir3;
3184
3209
  exports2.resolveBrowserDataDir = resolveBrowserDataDir;
3185
- exports2.listProfiles = listProfiles;
3210
+ exports2.listProfiles = listProfiles2;
3186
3211
  exports2.resolveProfileDir = resolveProfileDir;
3187
3212
  exports2.resolveCrossSyncDirs = resolveCrossSyncDirs2;
3188
3213
  exports2.loadSqliteDriver = loadSqliteDriver;
@@ -3194,7 +3219,7 @@ var require_utils = __commonJS({
3194
3219
  exports2.isBookmarkFile = isBookmarkFile;
3195
3220
  exports2.isHistoryEntry = isHistoryEntry;
3196
3221
  exports2.isLogLevel = isLogLevel2;
3197
- exports2.isBrowserName = isBrowserName2;
3222
+ exports2.isBrowserName = isBrowserName3;
3198
3223
  exports2.isCliConfig = isCliConfig3;
3199
3224
  var fs_1 = __importDefault(require("fs"));
3200
3225
  var os_1 = __importDefault(require("os"));
@@ -3314,7 +3339,7 @@ var require_utils = __commonJS({
3314
3339
  const key = platform === "darwin" ? "darwin" : "linux";
3315
3340
  return path_1.default.join(home, ...segments[key]);
3316
3341
  }
3317
- function listProfiles(dataDir) {
3342
+ function listProfiles2(dataDir) {
3318
3343
  let entries;
3319
3344
  try {
3320
3345
  entries = fs_1.default.readdirSync(dataDir, { withFileTypes: true });
@@ -3419,7 +3444,7 @@ var require_utils = __commonJS({
3419
3444
  function isLogLevel2(value) {
3420
3445
  return typeof value === "string" && constants_1.LOG_LEVELS.includes(value);
3421
3446
  }
3422
- function isBrowserName2(value) {
3447
+ function isBrowserName3(value) {
3423
3448
  return typeof value === "string" && constants_1.BROWSERS.includes(value);
3424
3449
  }
3425
3450
  function isCliConfig3(value) {
@@ -3785,11 +3810,40 @@ var require_chromium = __commonJS({
3785
3810
  return mod && mod.__esModule ? mod : { "default": mod };
3786
3811
  };
3787
3812
  Object.defineProperty(exports2, "__esModule", { value: true });
3813
+ exports2.readProfileInfos = readProfileInfos;
3788
3814
  exports2.toHistoryEntry = toHistoryEntry;
3789
3815
  exports2.countBookmarkFileNodes = countBookmarkFileNodes;
3790
- exports2.createBrowserReader = createBrowserReader2;
3816
+ exports2.createBrowserReader = createBrowserReader3;
3791
3817
  var path_1 = __importDefault(require("path"));
3792
3818
  var shared_1 = require_dist();
3819
+ function readProfileInfos(dataDir) {
3820
+ const localState = (0, shared_1.readJsonFile)(path_1.default.join(dataDir, shared_1.LOCAL_STATE_FILE_NAME));
3821
+ const cache = localState?.profile?.info_cache ?? {};
3822
+ return (0, shared_1.listProfiles)(dataDir).map((profile) => toProfileInfo(profile, cache[profile]));
3823
+ }
3824
+ function toProfileInfo(profile, raw) {
3825
+ const info = { profile };
3826
+ const name = readString(raw, "name");
3827
+ const userName = readString(raw, "user_name");
3828
+ const gaiaName = readString(raw, "gaia_name");
3829
+ if (name !== void 0) {
3830
+ info.name = name;
3831
+ }
3832
+ if (userName !== void 0) {
3833
+ info.userName = userName;
3834
+ }
3835
+ if (gaiaName !== void 0) {
3836
+ info.gaiaName = gaiaName;
3837
+ }
3838
+ if (typeof raw?.is_using_default_name === "boolean") {
3839
+ info.isUsingDefaultName = raw.is_using_default_name;
3840
+ }
3841
+ return info;
3842
+ }
3843
+ function readString(raw, key) {
3844
+ const value = raw?.[key];
3845
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3846
+ }
3793
3847
  function toHistoryEntry(row) {
3794
3848
  return {
3795
3849
  id: typeof row.id === "number" ? row.id : void 0,
@@ -3810,17 +3864,20 @@ var require_chromium = __commonJS({
3810
3864
  }
3811
3865
  return total;
3812
3866
  }
3813
- function createBrowserReader2(browser, options = {}) {
3867
+ function createBrowserReader3(browser, options = {}) {
3814
3868
  const dataDir = options.dataDir ?? (0, shared_1.resolveBrowserDataDir)(browser, { platform: options.platform, env: options.env });
3815
3869
  const bookmarksPath = (profile) => path_1.default.join((0, shared_1.resolveProfileDir)(dataDir, profile), shared_1.BOOKMARKS_FILE_NAME);
3816
3870
  const historyPath = (profile) => path_1.default.join((0, shared_1.resolveProfileDir)(dataDir, profile), shared_1.HISTORY_FILE_NAME);
3817
3871
  const profiles = () => (0, shared_1.listProfiles)(dataDir);
3872
+ const profileInfos = () => readProfileInfos(dataDir);
3873
+ const profileInfo = (profile) => profileInfos().find((info) => info.profile === profile) ?? null;
3818
3874
  const resolveSelectedProfiles = (profile) => {
3819
3875
  const found = profiles();
3820
- if (profile === "all") {
3876
+ if (profile.trim() === "all") {
3821
3877
  return found;
3822
3878
  }
3823
- return found.includes(profile) ? [profile] : [];
3879
+ const requested = (0, shared_1.parseProfileList)(profile);
3880
+ return requested.filter((name) => found.includes(name));
3824
3881
  };
3825
3882
  const readBookmarks = (profile) => {
3826
3883
  const filePath = bookmarksPath(profile);
@@ -3843,6 +3900,8 @@ var require_chromium = __commonJS({
3843
3900
  browser,
3844
3901
  dataDir,
3845
3902
  profiles,
3903
+ profileInfos,
3904
+ profileInfo,
3846
3905
  resolveProfiles: resolveSelectedProfiles,
3847
3906
  bookmarksPath,
3848
3907
  historyPath,
@@ -3886,12 +3945,12 @@ var require_readers = __commonJS({
3886
3945
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
3887
3946
  };
3888
3947
  Object.defineProperty(exports2, "__esModule", { value: true });
3889
- exports2.createConfiguredBrowserReaders = createConfiguredBrowserReaders3;
3948
+ exports2.createConfiguredBrowserReaders = createConfiguredBrowserReaders5;
3890
3949
  exports2.supportedBrowsers = supportedBrowsers;
3891
3950
  var shared_1 = require_dist();
3892
3951
  var chromium_1 = require_chromium();
3893
3952
  __exportStar(require_chromium(), exports2);
3894
- function createConfiguredBrowserReaders3(config, options = {}) {
3953
+ function createConfiguredBrowserReaders5(config, options = {}) {
3895
3954
  const readers = [];
3896
3955
  for (const browser of shared_1.BROWSERS) {
3897
3956
  const browserConfig = config.browsers[browser];
@@ -4197,13 +4256,16 @@ var require_sync = __commonJS({
4197
4256
  for (const reader of readers) {
4198
4257
  const profiles = reader.resolveProfiles(config.profile);
4199
4258
  if (profiles.length === 0) {
4200
- warnings.push(`No ${reader.browser} profile found under ${reader.dataDir}.`);
4259
+ const available = reader.profiles();
4260
+ const suffix = available.length > 0 ? ` Available: ${available.join(", ")}.` : "";
4261
+ warnings.push(`No ${reader.browser} profile found under ${reader.dataDir}.${suffix}`);
4201
4262
  continue;
4202
4263
  }
4203
4264
  for (const profile of profiles) {
4204
4265
  const outcome = {
4205
4266
  browser: reader.browser,
4206
4267
  profile,
4268
+ profileInfo: reader.profileInfo(profile),
4207
4269
  bookmarksRead: 0,
4208
4270
  historyRead: 0,
4209
4271
  writtenBack: false,
@@ -4368,7 +4430,7 @@ var require_dist2 = __commonJS({
4368
4430
  "../core/dist/index.js"(exports2) {
4369
4431
  "use strict";
4370
4432
  Object.defineProperty(exports2, "__esModule", { value: true });
4371
- exports2.writeBackBookmarks = exports2.runSync = exports2.pruneBackups = exports2.listBackups = exports2.formatBackupId = exports2.createBackup = exports2.backupFileName = exports2.BACKUP_MANIFEST_FILE = exports2.mergeHistory = exports2.mergeBookmarkFiles = exports2.toHistoryEntry = exports2.supportedBrowsers = exports2.createConfiguredBrowserReaders = exports2.createBrowserReader = exports2.countBookmarkFileNodes = exports2.parseProcessOutput = exports2.isBrowserRunning = exports2.getRunningBrowsers = exports2.getRunningBrowserNames = exports2.getProcessName = exports2.buildProcessQuery = exports2.readLogEntries = exports2.clearLogFile = exports2.Logger = exports2.MAX_BUFFERED_LOG_ENTRIES = exports2.DEFAULT_LOG_LIMIT = exports2.DEFAULT_BACKUP_RETENTION = exports2.BROWSER_PROCESS_NAMES = exports2.VERSION = exports2.PACKAGE = void 0;
4433
+ exports2.writeBackBookmarks = exports2.runSync = exports2.pruneBackups = exports2.listBackups = exports2.formatBackupId = exports2.createBackup = exports2.backupFileName = exports2.BACKUP_MANIFEST_FILE = exports2.mergeHistory = exports2.mergeBookmarkFiles = exports2.toHistoryEntry = exports2.supportedBrowsers = exports2.readProfileInfos = exports2.createConfiguredBrowserReaders = exports2.createBrowserReader = exports2.countBookmarkFileNodes = exports2.parseProcessOutput = exports2.isBrowserRunning = exports2.getRunningBrowsers = exports2.getRunningBrowserNames = exports2.getProcessName = exports2.buildProcessQuery = exports2.readLogEntries = exports2.clearLogFile = exports2.Logger = exports2.MAX_BUFFERED_LOG_ENTRIES = exports2.DEFAULT_LOG_LIMIT = exports2.DEFAULT_BACKUP_RETENTION = exports2.BROWSER_PROCESS_NAMES = exports2.VERSION = exports2.PACKAGE = void 0;
4372
4434
  var constants_1 = require_constants2();
4373
4435
  Object.defineProperty(exports2, "PACKAGE", { enumerable: true, get: function() {
4374
4436
  return constants_1.CORE_PACKAGE;
@@ -4428,6 +4490,9 @@ var require_dist2 = __commonJS({
4428
4490
  Object.defineProperty(exports2, "createConfiguredBrowserReaders", { enumerable: true, get: function() {
4429
4491
  return readers_1.createConfiguredBrowserReaders;
4430
4492
  } });
4493
+ Object.defineProperty(exports2, "readProfileInfos", { enumerable: true, get: function() {
4494
+ return readers_1.readProfileInfos;
4495
+ } });
4431
4496
  Object.defineProperty(exports2, "supportedBrowsers", { enumerable: true, get: function() {
4432
4497
  return readers_1.supportedBrowsers;
4433
4498
  } });
@@ -4501,7 +4566,7 @@ var {
4501
4566
  } = import_index.default;
4502
4567
 
4503
4568
  // src/constants.ts
4504
- var CLI_VERSION = "0.1.1";
4569
+ var CLI_VERSION = "0.1.3";
4505
4570
  var CLI_NAME = "crosssync";
4506
4571
  var CLI_DESCRIPTION = "Cross-browser bookmark & history sync CLI (Chrome + Edge)";
4507
4572
  var WINDOWS_TASK_NAME = "CrossSync";
@@ -4573,7 +4638,9 @@ function readMergedData(config, options = {}) {
4573
4638
  }
4574
4639
  const profiles = reader.resolveProfiles(options.profile ?? config.profile);
4575
4640
  if (profiles.length === 0) {
4576
- warnings.push(`No ${reader.browser} profile found under ${reader.dataDir}.`);
4641
+ const available = reader.profiles();
4642
+ const suffix = available.length > 0 ? ` Available: ${available.join(", ")}.` : "";
4643
+ warnings.push(`No ${reader.browser} profile found under ${reader.dataDir}.${suffix}`);
4577
4644
  continue;
4578
4645
  }
4579
4646
  for (const profile of profiles) {
@@ -5450,8 +5517,27 @@ function pruneExports(exportDir, keep) {
5450
5517
  }
5451
5518
 
5452
5519
  // src/commands/sync.ts
5520
+ var readline = __toESM(require("readline/promises"));
5453
5521
  var import_shared10 = __toESM(require_dist());
5454
5522
  var import_core4 = __toESM(require_dist2());
5523
+
5524
+ // src/profile-info.ts
5525
+ function formatProfileAccount(info) {
5526
+ if (!info) {
5527
+ return null;
5528
+ }
5529
+ const displayName = info.isUsingDefaultName ? info.gaiaName ?? info.name : info.name ?? info.gaiaName;
5530
+ const parts = [];
5531
+ if (displayName) {
5532
+ parts.push(displayName);
5533
+ }
5534
+ if (info.userName && info.userName !== displayName) {
5535
+ parts.push(`<${info.userName}>`);
5536
+ }
5537
+ return parts.length > 0 ? parts.join(" ") : null;
5538
+ }
5539
+
5540
+ // src/commands/sync.ts
5455
5541
  function resolveTargets(options) {
5456
5542
  if (options.all) {
5457
5543
  return { bookmarks: true, history: true };
@@ -5498,9 +5584,117 @@ function buildSyncConfig(config, options) {
5498
5584
  }
5499
5585
  };
5500
5586
  }
5501
- function runSyncCommand(context, options = {}) {
5587
+ async function ensureProfilesSelected(context, options) {
5588
+ if (options.profile !== void 0 || options.json) {
5589
+ return;
5590
+ }
5591
+ const interactive = options.selectProfiles !== void 0 || process.stdin.isTTY === true && process.stdout.isTTY === true;
5592
+ if (!interactive) {
5593
+ return;
5594
+ }
5595
+ const effective = buildSyncConfig(context.config, options);
5596
+ const readers = (0, import_core4.createConfiguredBrowserReaders)(effective, { platform: options.platform });
5597
+ const select = options.selectProfiles ?? promptForProfiles;
5598
+ let changed = false;
5599
+ for (const reader of readers) {
5600
+ if (reader.resolveProfiles(context.config.profile).length > 0) {
5601
+ continue;
5602
+ }
5603
+ const available = reader.profiles();
5604
+ if (available.length === 0) {
5605
+ continue;
5606
+ }
5607
+ const selected = await select({
5608
+ browser: reader.browser,
5609
+ dataDir: reader.dataDir,
5610
+ available,
5611
+ infos: reader.profileInfos(),
5612
+ current: context.config.profile
5613
+ });
5614
+ if (!selected || selected.length === 0) {
5615
+ continue;
5616
+ }
5617
+ const valid = selected.filter((name) => available.includes(name));
5618
+ if (valid.length === 0) {
5619
+ continue;
5620
+ }
5621
+ context.config.profile = (0, import_shared10.addProfilesToSelection)(context.config.profile, valid);
5622
+ changed = true;
5623
+ }
5624
+ if (changed) {
5625
+ saveConfigFile(context.dirs.configFile, context.config);
5626
+ context.io.out(`Profile selection saved: ${context.config.profile}`);
5627
+ }
5628
+ }
5629
+ async function promptForProfiles(request) {
5630
+ const output = process.stdout;
5631
+ output.write(`
5632
+ No ${request.browser} profile matched "${request.current}".
5633
+ `);
5634
+ output.write(`Available ${request.browser} profiles in ${request.dataDir}:
5635
+ `);
5636
+ const infos = new Map((request.infos ?? []).map((info) => [info.profile, info]));
5637
+ request.available.forEach((name, index) => {
5638
+ const account = formatProfileAccount(infos.get(name));
5639
+ output.write(` ${index + 1}) ${name}${account ? ` ${account}` : ""}
5640
+ `);
5641
+ });
5642
+ const rl = readline.createInterface({ input: process.stdin, output });
5643
+ try {
5644
+ for (; ; ) {
5645
+ const answer = await rl.question(
5646
+ 'Select profiles (comma-separated numbers or names, "all", empty to skip): '
5647
+ );
5648
+ const parsed = parseProfileAnswer(answer, request.available);
5649
+ if (parsed !== void 0) {
5650
+ return parsed;
5651
+ }
5652
+ output.write("Invalid selection; please try again.\n");
5653
+ }
5654
+ } catch {
5655
+ return null;
5656
+ } finally {
5657
+ rl.close();
5658
+ }
5659
+ }
5660
+ function parseProfileAnswer(answer, available) {
5661
+ const trimmed = answer.trim();
5662
+ if (trimmed.length === 0) {
5663
+ return null;
5664
+ }
5665
+ if (trimmed.toLowerCase() === "all") {
5666
+ return [...available];
5667
+ }
5668
+ const selected = [];
5669
+ for (const token of trimmed.split(",")) {
5670
+ const value = token.trim();
5671
+ if (value.length === 0) {
5672
+ continue;
5673
+ }
5674
+ if (/^\d+$/.test(value)) {
5675
+ const index = Number(value) - 1;
5676
+ if (index < 0 || index >= available.length) {
5677
+ return void 0;
5678
+ }
5679
+ const name = available[index];
5680
+ if (!selected.includes(name)) {
5681
+ selected.push(name);
5682
+ }
5683
+ continue;
5684
+ }
5685
+ if (!available.includes(value)) {
5686
+ return void 0;
5687
+ }
5688
+ if (!selected.includes(value)) {
5689
+ selected.push(value);
5690
+ }
5691
+ }
5692
+ return selected.length > 0 ? selected : void 0;
5693
+ }
5694
+ async function runSyncCommand(context, options = {}) {
5502
5695
  const targets = resolveTargets(options);
5503
5696
  const strategy = resolveStrategy(options.strategy) ?? context.config.sync.strategy;
5697
+ await ensureProfilesSelected(context, options);
5504
5698
  const config = buildSyncConfig(context.config, options);
5505
5699
  const report = (0, import_core4.runSync)({
5506
5700
  config,
@@ -5537,7 +5731,8 @@ function formatSyncReport(report) {
5537
5731
  ].filter((kind) => kind !== null).join(" + ");
5538
5732
  lines.push(`CrossSync sync (${report.strategy}, ${kinds})${report.dryRun ? " [dry run]" : ""}`);
5539
5733
  for (const outcome of report.browsers) {
5540
- const parts = [`${outcome.browser}/${outcome.profile}`];
5734
+ const account = formatProfileAccount(outcome.profileInfo);
5735
+ const parts = [`${outcome.browser}/${outcome.profile}${account ? ` \u2014 ${account}` : ""}`];
5541
5736
  if (report.targets.bookmarks) {
5542
5737
  parts.push(`${outcome.bookmarksRead} bookmark nodes`);
5543
5738
  }
@@ -5694,14 +5889,136 @@ function formatLogEntry(entry) {
5694
5889
  return `${timestamp} [${entry.level.toUpperCase()}] ${entry.message}${meta}`;
5695
5890
  }
5696
5891
 
5697
- // src/commands/status.ts
5698
- var import_path7 = __toESM(require("path"));
5892
+ // src/commands/profiles.ts
5699
5893
  var import_shared13 = __toESM(require_dist());
5700
5894
  var import_core7 = __toESM(require_dist2());
5895
+ function parseProfileNames(value) {
5896
+ if (value.trim().toLowerCase() === "all") {
5897
+ return "all";
5898
+ }
5899
+ const names = (0, import_shared13.parseProfileList)(value);
5900
+ if (names.length === 0) {
5901
+ throw new Error("Profile list is empty.");
5902
+ }
5903
+ for (const name of names) {
5904
+ if (!import_shared13.PROFILE_NAME_PATTERN.test(name)) {
5905
+ throw new Error(`Invalid profile name "${name}". Use "Default" or "Profile N".`);
5906
+ }
5907
+ }
5908
+ return names;
5909
+ }
5910
+ function listProfiles(context, options) {
5911
+ const filter = options.browser ?? "all";
5912
+ if (filter !== "all" && !(0, import_shared13.isBrowserName)(filter)) {
5913
+ context.io.err(`Unknown browser "${filter}". Use one of: ${import_shared13.BROWSERS.join(", ")}, all.`);
5914
+ return 1;
5915
+ }
5916
+ const selection = context.config.profile;
5917
+ const selected = selection.trim() === "all" ? null : (0, import_shared13.parseProfileList)(selection);
5918
+ for (const browser of import_shared13.BROWSERS) {
5919
+ if (filter !== "all" && browser !== filter) {
5920
+ continue;
5921
+ }
5922
+ const browserConfig = context.config.browsers[browser];
5923
+ const reader = (0, import_core7.createBrowserReader)(browser, {
5924
+ dataDir: browserConfig.dataDir,
5925
+ platform: options.platform,
5926
+ env: options.env
5927
+ });
5928
+ const available = reader.profiles();
5929
+ context.io.out(`${browser}: ${reader.dataDir}${browserConfig.enabled ? "" : " [disabled]"}`);
5930
+ if (available.length === 0) {
5931
+ context.io.out(" (no profiles found)");
5932
+ continue;
5933
+ }
5934
+ const infos = new Map(reader.profileInfos().map((info) => [info.profile, info]));
5935
+ for (const name of available) {
5936
+ const isSelected = selected === null || selected.includes(name);
5937
+ const account = formatProfileAccount(infos.get(name));
5938
+ context.io.out(
5939
+ ` ${name}${account ? ` ${account}` : ""}${isSelected ? " [selected]" : ""}`
5940
+ );
5941
+ }
5942
+ const missing = selected?.filter((name) => !available.includes(name)) ?? [];
5943
+ if (missing.length > 0) {
5944
+ context.io.out(` (configured but not found: ${missing.join(", ")})`);
5945
+ }
5946
+ }
5947
+ context.io.out(`Selected profile(s): ${selection}`);
5948
+ return 0;
5949
+ }
5950
+ function expandAllProfiles(context, options) {
5951
+ const names = [];
5952
+ const readers = (0, import_core7.createConfiguredBrowserReaders)(context.config, {
5953
+ platform: options.platform,
5954
+ env: options.env
5955
+ });
5956
+ for (const reader of readers) {
5957
+ for (const name of reader.profiles()) {
5958
+ if (!names.includes(name)) {
5959
+ names.push(name);
5960
+ }
5961
+ }
5962
+ }
5963
+ return names;
5964
+ }
5965
+ function removeProfiles(context, current, names, options) {
5966
+ if (names === "all") {
5967
+ throw new Error("Refusing to remove every profile; use --reset or --set instead.");
5968
+ }
5969
+ const base = current.trim() === "all" ? expandAllProfiles(context, options).join(",") : current;
5970
+ const remaining = (0, import_shared13.parseProfileList)(base).filter((name) => !names.includes(name));
5971
+ if (remaining.length === (0, import_shared13.parseProfileList)(base).length) {
5972
+ throw new Error(`None of the selected profiles matched: ${names.join(", ")}.`);
5973
+ }
5974
+ if (remaining.length === 0) {
5975
+ throw new Error("Refusing to remove every profile; use --reset or --set instead.");
5976
+ }
5977
+ return remaining.join(",");
5978
+ }
5979
+ function runProfilesCommand(context, options = {}) {
5980
+ const mutating = options.set !== void 0 || options.add !== void 0 || options.remove !== void 0 || options.all === true || options.reset === true;
5981
+ if (!mutating) {
5982
+ return listProfiles(context, options);
5983
+ }
5984
+ if (options.browser !== void 0 && options.browser !== "all") {
5985
+ context.io.err("--browser only applies when listing; profile changes are global.");
5986
+ return 1;
5987
+ }
5988
+ try {
5989
+ let next = context.config.profile;
5990
+ if (options.reset) {
5991
+ next = import_shared13.DEFAULT_PROFILE;
5992
+ } else if (options.all) {
5993
+ next = "all";
5994
+ } else if (options.set !== void 0) {
5995
+ const names = parseProfileNames(options.set);
5996
+ next = names === "all" ? "all" : names.join(",");
5997
+ } else if (options.add !== void 0) {
5998
+ const names = parseProfileNames(options.add);
5999
+ next = names === "all" ? "all" : (0, import_shared13.addProfilesToSelection)(next, names);
6000
+ } else if (options.remove !== void 0) {
6001
+ next = removeProfiles(context, next, parseProfileNames(options.remove), options);
6002
+ }
6003
+ const nextConfig = { ...context.config, profile: next };
6004
+ saveConfigFile(context.dirs.configFile, nextConfig);
6005
+ context.config = nextConfig;
6006
+ context.io.out(`profile = ${JSON.stringify(next)}`);
6007
+ return 0;
6008
+ } catch (error) {
6009
+ context.io.err(error instanceof Error ? error.message : String(error));
6010
+ return 1;
6011
+ }
6012
+ }
6013
+
6014
+ // src/commands/status.ts
6015
+ var import_path7 = __toESM(require("path"));
6016
+ var import_shared14 = __toESM(require_dist());
6017
+ var import_core8 = __toESM(require_dist2());
5701
6018
  function buildStatusReport(context, options = {}) {
5702
6019
  const { config, dirs } = context;
5703
6020
  const running = new Map(
5704
- (0, import_core7.getRunningBrowsers)({ platform: options.platform }).map((status) => [
6021
+ (0, import_core8.getRunningBrowsers)({ platform: options.platform }).map((status) => [
5705
6022
  status.browser,
5706
6023
  status.running
5707
6024
  ])
@@ -5716,14 +6033,14 @@ function buildStatusReport(context, options = {}) {
5716
6033
  for (const profile of reader.resolveProfiles(config.profile)) {
5717
6034
  profiles.push({
5718
6035
  profile,
5719
- bookmarks: (0, import_shared13.pathExists)(reader.bookmarksPath(profile)),
5720
- history: (0, import_shared13.pathExists)(reader.historyPath(profile))
6036
+ bookmarks: (0, import_shared14.pathExists)(reader.bookmarksPath(profile)),
6037
+ history: (0, import_shared14.pathExists)(reader.historyPath(profile))
5721
6038
  });
5722
6039
  }
5723
6040
  browsers.push({
5724
6041
  browser,
5725
6042
  dataDir: reader.dataDir,
5726
- exists: (0, import_shared13.pathExists)(reader.dataDir),
6043
+ exists: (0, import_shared14.pathExists)(reader.dataDir),
5727
6044
  running: running.get(browser) ?? false,
5728
6045
  profiles
5729
6046
  });
@@ -5737,14 +6054,14 @@ function buildStatusReport(context, options = {}) {
5737
6054
  });
5738
6055
  }
5739
6056
  }
5740
- const backups = (0, import_core7.listBackups)(config.backupDir);
6057
+ const backups = (0, import_core8.listBackups)(config.backupDir);
5741
6058
  const exports2 = listExports(config.exportDir, 1);
5742
6059
  return {
5743
6060
  version: CLI_VERSION,
5744
6061
  configFile: dirs.configFile,
5745
6062
  dataDir: config.dataDir,
5746
6063
  logLevel: config.logLevel,
5747
- logFile: import_path7.default.join(config.logDir, import_shared13.LOG_FILE_NAME),
6064
+ logFile: import_path7.default.join(config.logDir, import_shared14.LOG_FILE_NAME),
5748
6065
  profile: config.profile,
5749
6066
  schedule: { ...config.schedule },
5750
6067
  browsers,
@@ -5757,7 +6074,7 @@ function buildStatusReport(context, options = {}) {
5757
6074
  };
5758
6075
  }
5759
6076
  function createReader(browser, dataDir, options) {
5760
- return (0, import_core7.createBrowserReader)(browser, {
6077
+ return (0, import_core8.createBrowserReader)(browser, {
5761
6078
  dataDir,
5762
6079
  platform: options.platform,
5763
6080
  env: options.env
@@ -5806,11 +6123,16 @@ function createCliContext(options = {}) {
5806
6123
  function buildProgram(contextFactory = createCliContext) {
5807
6124
  const program2 = new Command();
5808
6125
  program2.name(CLI_NAME).description(CLI_DESCRIPTION).version(CLI_VERSION, "-v, --version", "print the CrossSync version").option("--home-dir <path>", "use an alternate home directory").option("--verbose", "enable debug logging").option("--quiet", "only log errors");
5809
- const withContext = (action) => () => {
6126
+ const withContext = (action) => async () => {
5810
6127
  const context = contextFactory(program2.opts());
5811
- process.exitCode = action(context);
6128
+ try {
6129
+ process.exitCode = await action(context);
6130
+ } catch (error) {
6131
+ context.io.err(error instanceof Error ? error.message : String(error));
6132
+ process.exitCode = 1;
6133
+ }
5812
6134
  };
5813
- const syncCommand = program2.command("sync").description("read, merge and write back browser bookmarks/history").option("--bookmarks", "sync bookmarks only").option("--history", "sync history only").option("--all", "sync bookmarks and history (default)").option("--browser <name>", "chrome, edge or all", "all").option("--profile <name>", 'profile name or "all"').option("--strategy <name>", "merge (write back) or export (read only)").option("--dry-run", "show what would happen without touching any file").option("--force", "write bookmarks back even while the browser is running").option("--json", "print the report as JSON");
6135
+ const syncCommand = program2.command("sync").description("read, merge and write back browser bookmarks/history").option("--bookmarks", "sync bookmarks only").option("--history", "sync history only").option("--all", "sync bookmarks and history (default)").option("--browser <name>", "chrome, edge or all", "all").option("--profile <names>", 'profile name(s), comma-separated, or "all"').option("--strategy <name>", "merge (write back) or export (read only)").option("--dry-run", "show what would happen without touching any file").option("--force", "write bookmarks back even while the browser is running").option("--json", "print the report as JSON");
5814
6136
  syncCommand.action(withContext((context) => runSyncCommand(context, syncCommand.opts())));
5815
6137
  const backupCommand = program2.command("backup").description("back up browser bookmark and history files").option("--list", "list existing backups").option("--prune", "delete old backups").option("--keep <count>", "how many backups to keep when pruning");
5816
6138
  backupCommand.action(
@@ -5823,10 +6145,14 @@ function buildProgram(contextFactory = createCliContext) {
5823
6145
  });
5824
6146
  })
5825
6147
  );
5826
- const exportCommand = program2.command("export").description("export merged bookmarks/history as JSON").option("--format <format>", "output format (json)", "json").option("--out <dir>", "directory to write into").option("--browser <name>", "chrome, edge or all", "all").option("--profile <name>", 'profile name or "all"').option("--stdout", "print the export instead of writing a file");
6148
+ const exportCommand = program2.command("export").description("export merged bookmarks/history as JSON").option("--format <format>", "output format (json)", "json").option("--out <dir>", "directory to write into").option("--browser <name>", "chrome, edge or all", "all").option("--profile <names>", 'profile name(s), comma-separated, or "all"').option("--stdout", "print the export instead of writing a file");
5827
6149
  exportCommand.action(withContext((context) => runExportCommand(context, exportCommand.opts())));
5828
6150
  const statusCommand = program2.command("status").description("show browsers, profiles, backups and schedule state").option("--json", "print the status as JSON");
5829
6151
  statusCommand.action(withContext((context) => runStatusCommand(context, statusCommand.opts())));
6152
+ const profilesCommand = program2.command("profiles").description("list available Chromium profiles or change the synced selection").option("--list", "list profiles and the current selection (default)").option("--set <names>", 'replace the selection, e.g. "Default,Profile 11" or "all"').option("--add <names>", "add profiles to the selection").option("--remove <names>", "remove profiles from the selection").option("--all", "select every available profile").option("--reset", 'reset the selection to "Default"').option("--browser <name>", "limit listing to chrome, edge or all", "all");
6153
+ profilesCommand.action(
6154
+ withContext((context) => runProfilesCommand(context, profilesCommand.opts()))
6155
+ );
5830
6156
  const logsCommand = program2.command("logs").description("print CrossSync log entries").option("--limit <count>", "number of entries to show", "50").option("--level <level>", "debug, info, warn or error").option("--clear", "delete the log file").option("--json", "print the entries as JSON");
5831
6157
  logsCommand.action(
5832
6158
  withContext((context) => {
@@ -5851,13 +6177,13 @@ function buildProgram(contextFactory = createCliContext) {
5851
6177
  configCommand.action(withContext((context) => runConfigCommand(context, configCommand.opts())));
5852
6178
  return program2;
5853
6179
  }
5854
- function run(argv = process.argv) {
5855
- buildProgram().parse(argv);
6180
+ async function run(argv = process.argv) {
6181
+ await buildProgram().parseAsync(argv);
5856
6182
  }
5857
6183
  var PACKAGE = "crosssync";
5858
6184
  var VERSION = CLI_VERSION;
5859
6185
  if (require.main === module) {
5860
- run();
6186
+ void run();
5861
6187
  }
5862
6188
  // Annotate the CommonJS export names for ESM import in node:
5863
6189
  0 && (module.exports = {