claude-multi 0.8.1 → 0.9.0

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/build/cli.js CHANGED
@@ -7499,22 +7499,22 @@ var init_templates = __esm(() => {
7499
7499
  kimi: {
7500
7500
  name: "kimi",
7501
7501
  displayName: "Moonshot Kimi",
7502
- description: "Kimi K2.5 models via moonshot.ai — pay-per-token only, no subscription plan",
7502
+ description: "Kimi K2.7 Code, K2.6, K2.5 models via moonshot.ai — pay-per-token only, no subscription plan",
7503
7503
  settings: {
7504
7504
  env: {
7505
7505
  ANTHROPIC_AUTH_TOKEN: "",
7506
7506
  ANTHROPIC_BASE_URL: "https://api.moonshot.ai/anthropic",
7507
7507
  API_TIMEOUT_MS: "3000000",
7508
- ANTHROPIC_MODEL: "kimi-k2.5",
7509
- ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-k2.5",
7510
- ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-k2.5",
7508
+ ANTHROPIC_MODEL: "kimi-k2.6",
7509
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-k2.7-code",
7510
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-k2.6",
7511
7511
  ANTHROPIC_DEFAULT_HAIKU_MODEL: "kimi-k2.5",
7512
7512
  ANTHROPIC_SMALL_FAST_MODEL: "kimi-k2.5",
7513
7513
  ENABLE_THINKING: "true",
7514
7514
  REASONING_EFFORT: "high",
7515
7515
  MAX_THINKING_TOKENS: "16000",
7516
7516
  MAX_OUTPUT_TOKENS: "65536",
7517
- CLAUDE_CODE_AUTO_COMPACT_WINDOW: "131072",
7517
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: "262144",
7518
7518
  CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: "75"
7519
7519
  },
7520
7520
  includeCoAuthoredBy: false,
@@ -7652,7 +7652,14 @@ var init_errors = __esm(() => {
7652
7652
  });
7653
7653
 
7654
7654
  // src/constants.ts
7655
- var MigrationStatus, PluginCategory, McpServerType, CopyOption, PluginAction, McpAction;
7655
+ function canConvertSyncMode(from, to) {
7656
+ return SYNC_MODE_ORDER.indexOf(to) > SYNC_MODE_ORDER.indexOf(from);
7657
+ }
7658
+ function availableSyncModeConversions(current) {
7659
+ const idx = SYNC_MODE_ORDER.indexOf(current);
7660
+ return SYNC_MODE_ORDER.slice(idx + 1);
7661
+ }
7662
+ var MigrationStatus, PluginCategory, McpServerType, CopyOption, SyncMode, SYNC_MODE_ORDER, PluginAction, McpAction;
7656
7663
  var init_constants = __esm(() => {
7657
7664
  MigrationStatus = {
7658
7665
  Completed: "completed",
@@ -7676,6 +7683,12 @@ var init_constants = __esm(() => {
7676
7683
  All: "all",
7677
7684
  Mcp: "mcp"
7678
7685
  };
7686
+ SyncMode = {
7687
+ Auto: "auto",
7688
+ HalfManual: "half-manual",
7689
+ FullManual: "full-manual"
7690
+ };
7691
+ SYNC_MODE_ORDER = [SyncMode.Auto, SyncMode.HalfManual, SyncMode.FullManual];
7679
7692
  PluginAction = {
7680
7693
  List: "list",
7681
7694
  Enable: "enable",
@@ -10080,10 +10093,12 @@ var exports_config = {};
10080
10093
  __export(exports_config, {
10081
10094
  writeClaudeSettings: () => writeClaudeSettings,
10082
10095
  validatePluginOperation: () => validatePluginOperation,
10096
+ updateInstanceSyncMode: () => updateInstanceSyncMode,
10083
10097
  updateInstanceAutoSync: () => updateInstanceAutoSync,
10084
10098
  unsyncPluginsAndSkills: () => unsyncPluginsAndSkills,
10085
10099
  syncProviderTemplateForInstance: () => syncProviderTemplateForInstance,
10086
10100
  syncPluginsAndSkills: () => syncPluginsAndSkills,
10101
+ syncModeLabel: () => syncModeLabel,
10087
10102
  setTestDefaultClaudeDir: () => setTestDefaultClaudeDir,
10088
10103
  setTestConfigDir: () => setTestConfigDir,
10089
10104
  setEnabledPlugins: () => setEnabledPlugins,
@@ -10103,10 +10118,13 @@ __export(exports_config, {
10103
10118
  listDefaultPlugins: () => listDefaultPlugins,
10104
10119
  listAvailablePlugins: () => listAvailablePlugins,
10105
10120
  isPluginsSymlinked: () => isPluginsSymlinked,
10121
+ isHalfManualSync: () => isHalfManualSync,
10106
10122
  isClaudeCodeRunning: () => isClaudeCodeRunning,
10107
10123
  initializeInstanceState: () => initializeInstanceState,
10108
10124
  hasDefaultMcpConfig: () => hasDefaultMcpConfig,
10109
10125
  hasDefaultClaudeConfig: () => hasDefaultClaudeConfig,
10126
+ halfSyncPluginsAndSkills: () => halfSyncPluginsAndSkills,
10127
+ getSyncMode: () => getSyncMode,
10110
10128
  getMcpServersFromPlugins: () => getMcpServersFromPlugins,
10111
10129
  getInstanceMcpServers: () => getInstanceMcpServers,
10112
10130
  getInstance: () => getInstance,
@@ -10146,6 +10164,23 @@ function getConfigDir() {
10146
10164
  function getConfigFile() {
10147
10165
  return join4(getConfigDir(), "config.json");
10148
10166
  }
10167
+ function getSyncMode(instance) {
10168
+ if (instance.syncMode && VALID_SYNC_MODES.has(instance.syncMode))
10169
+ return instance.syncMode;
10170
+ if (instance.autoSync === false)
10171
+ return SyncMode.FullManual;
10172
+ return SyncMode.Auto;
10173
+ }
10174
+ function syncModeLabel(mode) {
10175
+ switch (mode) {
10176
+ case SyncMode.Auto:
10177
+ return "Auto-sync (symlink dirs)";
10178
+ case SyncMode.HalfManual:
10179
+ return "Half-manual (symlink items)";
10180
+ case SyncMode.FullManual:
10181
+ return "Full-manual (independent copy)";
10182
+ }
10183
+ }
10149
10184
  async function copyDirRecursive(source, target) {
10150
10185
  if (!existsSync4(target)) {
10151
10186
  await mkdir(target, { recursive: true });
@@ -10262,6 +10297,34 @@ async function updateInstanceAutoSync(name, autoSync) {
10262
10297
  }
10263
10298
  const inst = config.instances[index];
10264
10299
  inst.autoSync = autoSync;
10300
+ inst.syncMode = autoSync ? SyncMode.Auto : SyncMode.FullManual;
10301
+ await saveConfig(config);
10302
+ return inst;
10303
+ }
10304
+ async function updateInstanceSyncMode(name, newMode) {
10305
+ const config = await loadConfig();
10306
+ const index = config.instances.findIndex((i) => i.name === name);
10307
+ if (index === -1) {
10308
+ return null;
10309
+ }
10310
+ const inst = config.instances[index];
10311
+ const currentMode = getSyncMode(inst);
10312
+ if (currentMode === newMode)
10313
+ return inst;
10314
+ if (!canConvertSyncMode(currentMode, newMode)) {
10315
+ throw new ClaudeMultiError(ErrorCode.SYMLINK_CONFLICT, `Cannot convert from ${syncModeLabel(currentMode)} to ${syncModeLabel(newMode)}. Only downgrades are allowed.`);
10316
+ }
10317
+ if (newMode === SyncMode.Auto) {
10318
+ await syncPluginsAndSkills(inst.configDir);
10319
+ } else if (newMode === SyncMode.HalfManual) {
10320
+ if (currentMode === SyncMode.Auto) {
10321
+ await halfSyncPluginsAndSkills(inst.configDir);
10322
+ }
10323
+ } else if (newMode === SyncMode.FullManual) {
10324
+ await unsyncPluginsAndSkills(inst.configDir);
10325
+ }
10326
+ inst.syncMode = newMode;
10327
+ inst.autoSync = newMode === SyncMode.Auto ? true : false;
10265
10328
  await saveConfig(config);
10266
10329
  return inst;
10267
10330
  }
@@ -10303,8 +10366,20 @@ function detectBrokenSymlinks(configDir) {
10303
10366
  try {
10304
10367
  const stats = lstatSync(targetPath);
10305
10368
  result.all.push(dir);
10306
- if (stats.isSymbolicLink() && isBrokenSymlink(targetPath)) {
10307
- result.broken.push(dir);
10369
+ if (stats.isSymbolicLink()) {
10370
+ if (isBrokenSymlink(targetPath)) {
10371
+ result.broken.push(dir);
10372
+ }
10373
+ } else if (stats.isDirectory()) {
10374
+ try {
10375
+ const entries = readdirSync2(targetPath, { withFileTypes: true });
10376
+ for (const entry of entries) {
10377
+ const entryPath = join4(targetPath, entry.name);
10378
+ if (isBrokenSymlink(entryPath)) {
10379
+ result.broken.push(`${dir}/${entry.name}`);
10380
+ }
10381
+ }
10382
+ } catch {}
10308
10383
  }
10309
10384
  } catch {}
10310
10385
  }
@@ -10340,7 +10415,7 @@ async function copySettingsFromDefault(targetConfigDir) {
10340
10415
  const targetSettings = join4(targetConfigDir, "settings.json");
10341
10416
  await writeFile3(targetSettings, JSON.stringify(safeSettings, null, 2), "utf-8");
10342
10417
  }
10343
- async function copyAllFromDefault(targetConfigDir, autoSync = false) {
10418
+ async function copyAllFromDefault(targetConfigDir, syncModeOrAutoSync = false) {
10344
10419
  const defaultDir = getDefaultClaudeDir();
10345
10420
  if (!existsSync4(defaultDir)) {
10346
10421
  throw new ClaudeMultiError(ErrorCode.DEFAULT_DIR_NOT_FOUND, "Default Claude directory not found");
@@ -10348,6 +10423,7 @@ async function copyAllFromDefault(targetConfigDir, autoSync = false) {
10348
10423
  if (!existsSync4(targetConfigDir)) {
10349
10424
  await mkdir(targetConfigDir, { recursive: true });
10350
10425
  }
10426
+ const effectiveMode = typeof syncModeOrAutoSync === "boolean" ? syncModeOrAutoSync ? SyncMode.Auto : SyncMode.FullManual : syncModeOrAutoSync;
10351
10427
  const excludeFiles = [
10352
10428
  "config.json",
10353
10429
  ".config.json",
@@ -10375,23 +10451,46 @@ async function copyAllFromDefault(targetConfigDir, autoSync = false) {
10375
10451
  continue;
10376
10452
  }
10377
10453
  if (stat.isDirectory()) {
10378
- if (autoSync && syncDirSet.has(entry)) {
10379
- try {
10380
- const targetStat = lstatSync(targetPath);
10381
- if (targetStat.isSymbolicLink()) {
10382
- const currentTarget = readlinkSync(targetPath);
10383
- if (currentTarget === sourcePath) {
10384
- console.log(` ✓ Already symlinked: ${entry}`);
10385
- continue;
10454
+ if (syncDirSet.has(entry)) {
10455
+ if (effectiveMode === SyncMode.Auto) {
10456
+ try {
10457
+ const targetStat = lstatSync(targetPath);
10458
+ if (targetStat.isSymbolicLink()) {
10459
+ const currentTarget = readlinkSync(targetPath);
10460
+ if (currentTarget === sourcePath) {
10461
+ console.log(` ✓ Already symlinked: ${entry}`);
10462
+ continue;
10463
+ }
10464
+ console.log(source_default.yellow(` ⚠ Replacing incorrect symlink: ${entry}`));
10465
+ } else {
10466
+ console.log(source_default.yellow(` ⚠ Removing existing directory: ${entry}`));
10386
10467
  }
10387
- console.log(source_default.yellow(` ⚠ Replacing incorrect symlink: ${entry}`));
10388
- } else {
10389
- console.log(source_default.yellow(` ⚠ Removing existing directory: ${entry}`));
10468
+ rmSync2(targetPath, { force: true, recursive: true });
10469
+ } catch {}
10470
+ await symlink(sourcePath, targetPath, "dir");
10471
+ console.log(` Symlinked: ${entry} -> ${sourcePath}`);
10472
+ } else if (effectiveMode === SyncMode.HalfManual) {
10473
+ if (!existsSync4(targetPath)) {
10474
+ await mkdir(targetPath, { recursive: true });
10475
+ }
10476
+ const subEntries = readdirSync2(sourcePath, { withFileTypes: true });
10477
+ let linked = 0;
10478
+ for (const sub of subEntries) {
10479
+ const subSource = join4(sourcePath, sub.name);
10480
+ const subTarget = join4(targetPath, sub.name);
10481
+ if (lstatSafe(subTarget))
10482
+ continue;
10483
+ const rel = relative(dirname4(subTarget), subSource);
10484
+ await symlink(rel, subTarget, sub.isDirectory() ? "dir" : "file");
10485
+ linked++;
10390
10486
  }
10391
- rmSync2(targetPath, { force: true, recursive: true });
10392
- } catch {}
10393
- await symlink(sourcePath, targetPath, "dir");
10394
- console.log(` Symlinked: ${entry} -> ${sourcePath}`);
10487
+ console.log(` Half-synced: ${entry} (${linked} items individually symlinked)`);
10488
+ } else {
10489
+ if (!existsSync4(targetPath)) {
10490
+ await mkdir(targetPath, { recursive: true });
10491
+ }
10492
+ await copyRecursive(sourcePath, targetPath);
10493
+ }
10395
10494
  } else {
10396
10495
  if (!existsSync4(targetPath)) {
10397
10496
  await mkdir(targetPath, { recursive: true });
@@ -10653,6 +10752,58 @@ async function copyFilesRecursive(source, target) {
10653
10752
  }
10654
10753
  }));
10655
10754
  }
10755
+ async function halfSyncPluginsAndSkills(configDir) {
10756
+ const defaultDir = getDefaultClaudeDir();
10757
+ if (!existsSync4(configDir)) {
10758
+ throw new ClaudeMultiError(ErrorCode.INSTANCE_DIR_NOT_FOUND, "Instance config directory does not exist");
10759
+ }
10760
+ await Promise.all(SYNC_DIRS.map(async (dir) => {
10761
+ const targetPath = join4(configDir, dir);
10762
+ const sourcePath = join4(defaultDir, dir);
10763
+ if (!existsSync4(sourcePath)) {
10764
+ console.log(source_default.yellow(` ⚠ Source ${dir} not found in ${defaultDir}, skipping`));
10765
+ return;
10766
+ }
10767
+ let wasWholeDirSymlink = false;
10768
+ try {
10769
+ const stat = lstatSync(targetPath);
10770
+ if (stat.isSymbolicLink()) {
10771
+ rmSync2(targetPath, { force: true });
10772
+ wasWholeDirSymlink = true;
10773
+ console.log(source_default.gray(` ✓ Removed directory symlink for ${dir}`));
10774
+ }
10775
+ } catch {}
10776
+ if (!existsSync4(targetPath)) {
10777
+ await mkdir(targetPath, { recursive: true });
10778
+ }
10779
+ const entries = readdirSync2(sourcePath, { withFileTypes: true });
10780
+ let linked = 0;
10781
+ for (const entry of entries) {
10782
+ const sourceEntry = join4(sourcePath, entry.name);
10783
+ const targetEntry = join4(targetPath, entry.name);
10784
+ const existingStat = lstatSafe(targetEntry);
10785
+ if (existingStat) {
10786
+ if (existingStat.isSymbolicLink()) {
10787
+ try {
10788
+ const currentTarget = readlinkSync(targetEntry);
10789
+ const resolvedTarget = isAbsolute(currentTarget) ? currentTarget : resolve2(dirname4(targetEntry), currentTarget);
10790
+ if (resolvedTarget === sourceEntry) {
10791
+ linked++;
10792
+ continue;
10793
+ }
10794
+ } catch {}
10795
+ rmSync2(targetEntry, { force: true });
10796
+ } else {
10797
+ continue;
10798
+ }
10799
+ }
10800
+ const relativePath = relative(dirname4(targetEntry), sourceEntry);
10801
+ await symlink(relativePath, targetEntry, entry.isDirectory() ? "dir" : "file");
10802
+ linked++;
10803
+ }
10804
+ console.log(source_default.green(` ✓ Half-synced ${dir}: ${linked} item(s) individually symlinked`));
10805
+ }));
10806
+ }
10656
10807
  async function unsyncPluginsAndSkills(configDir) {
10657
10808
  const defaultDir = getDefaultClaudeDir();
10658
10809
  if (!existsSync4(configDir)) {
@@ -10665,21 +10816,63 @@ async function unsyncPluginsAndSkills(configDir) {
10665
10816
  console.log(source_default.yellow(` ⚠ Source ${dir} not found in ${defaultDir}, skipping`));
10666
10817
  return;
10667
10818
  }
10668
- let isSymlink = false;
10819
+ let isWholeDirSymlink = false;
10669
10820
  try {
10670
10821
  readlinkSync(targetPath);
10671
- isSymlink = true;
10822
+ isWholeDirSymlink = true;
10672
10823
  } catch {}
10673
- if (isSymlink) {
10824
+ if (isWholeDirSymlink) {
10674
10825
  rmSync2(targetPath, { force: true });
10675
- console.log(source_default.gray(` ✓ Removed symlink for ${dir}`));
10676
- } else if (!existsSync4(targetPath)) {} else {
10677
- console.log(source_default.yellow(` ⚠ ${dir} is already a regular directory, skipping`));
10678
- return;
10826
+ console.log(source_default.gray(` ✓ Removed directory symlink for ${dir}`));
10827
+ await mkdir(targetPath, { recursive: true });
10828
+ await copyFilesRecursive(sourcePath, targetPath);
10829
+ console.log(source_default.green(` ✓ Copied files for ${dir}`));
10830
+ } else if (!existsSync4(targetPath)) {
10831
+ await mkdir(targetPath, { recursive: true });
10832
+ await copyFilesRecursive(sourcePath, targetPath);
10833
+ console.log(source_default.green(` ✓ Copied files for ${dir}`));
10834
+ } else {
10835
+ let replaced = 0;
10836
+ const entries = readdirSync2(targetPath, { withFileTypes: true });
10837
+ for (const entry of entries) {
10838
+ const entryPath = join4(targetPath, entry.name);
10839
+ const entryStat = lstatSafe(entryPath);
10840
+ if (entryStat?.isSymbolicLink()) {
10841
+ const linkTarget = readlinkSync(entryPath);
10842
+ const resolvedTarget = isAbsolute(linkTarget) ? linkTarget : resolve2(dirname4(entryPath), linkTarget);
10843
+ rmSync2(entryPath, { force: true });
10844
+ if (existsSync4(resolvedTarget)) {
10845
+ const realStat = statSync(resolvedTarget);
10846
+ if (realStat.isDirectory()) {
10847
+ await mkdir(entryPath, { recursive: true });
10848
+ await copyDirRecursive(resolvedTarget, entryPath);
10849
+ } else {
10850
+ await copyFile(resolvedTarget, entryPath);
10851
+ }
10852
+ replaced++;
10853
+ }
10854
+ }
10855
+ }
10856
+ const sourceEntries = readdirSync2(sourcePath, { withFileTypes: true });
10857
+ for (const entry of sourceEntries) {
10858
+ const targetEntry = join4(targetPath, entry.name);
10859
+ if (!existsSync4(targetEntry)) {
10860
+ const sourceEntry = join4(sourcePath, entry.name);
10861
+ if (entry.isDirectory()) {
10862
+ await mkdir(targetEntry, { recursive: true });
10863
+ await copyDirRecursive(sourceEntry, targetEntry);
10864
+ } else {
10865
+ await copyFile(sourceEntry, targetEntry);
10866
+ }
10867
+ replaced++;
10868
+ }
10869
+ }
10870
+ if (replaced > 0) {
10871
+ console.log(source_default.green(` ✓ ${dir}: replaced ${replaced} symlink(s) with copies`));
10872
+ } else {
10873
+ console.log(source_default.gray(` ✓ ${dir}: already fully independent`));
10874
+ }
10679
10875
  }
10680
- await mkdir(targetPath, { recursive: true });
10681
- await copyFilesRecursive(sourcePath, targetPath);
10682
- console.log(source_default.green(` ✓ Copied files for ${dir}`));
10683
10876
  }));
10684
10877
  }
10685
10878
  async function readClaudeSettings(configDir) {
@@ -10820,6 +11013,26 @@ function isPluginsSymlinked(configDir) {
10820
11013
  const st = lstatSafe(pluginsPath);
10821
11014
  return st?.isSymbolicLink() ?? false;
10822
11015
  }
11016
+ function isHalfManualSync(configDir) {
11017
+ for (const dir of SYNC_DIRS) {
11018
+ const dirPath = join4(configDir, dir);
11019
+ if (!existsSync4(dirPath))
11020
+ continue;
11021
+ const st = lstatSafe(dirPath);
11022
+ if (st?.isDirectory() && !st.isSymbolicLink()) {
11023
+ try {
11024
+ const entries = readdirSync2(dirPath, { withFileTypes: true });
11025
+ for (const entry of entries) {
11026
+ const entryPath = join4(dirPath, entry.name);
11027
+ const entryStat = lstatSafe(entryPath);
11028
+ if (entryStat?.isSymbolicLink())
11029
+ return true;
11030
+ }
11031
+ } catch {}
11032
+ }
11033
+ }
11034
+ return false;
11035
+ }
10823
11036
  function isClaudeCodeRunning(configDir) {
10824
11037
  if (false)
10825
11038
  ;
@@ -10918,6 +11131,9 @@ function validatePluginOperation(configDir, operation, pluginId) {
10918
11131
  if (existsSync4(configDir) && isPluginsSymlinked(configDir)) {
10919
11132
  errors.push("Plugins directory is symlinked (auto-sync). Disable auto-sync first.");
10920
11133
  }
11134
+ if (existsSync4(configDir) && !isPluginsSymlinked(configDir) && isHalfManualSync(configDir)) {
11135
+ errors.push("Plugins are individually symlinked (half-manual). Switch to full-manual first to manage individual plugins.");
11136
+ }
10921
11137
  if (operation === PluginAction.Install && pluginId) {
10922
11138
  const defaults = listDefaultPlugins();
10923
11139
  if (!defaults.find((p) => p.id === pluginId)) {
@@ -11082,7 +11298,7 @@ async function removeCustomMcpServer(configDir, name) {
11082
11298
  delete settings.mcpServers[name];
11083
11299
  await writeClaudeSettings(configDir, settings);
11084
11300
  }
11085
- var _testConfigDir, SYNC_DIRS, _testDefaultClaudeDir, MARKETPLACE_REL = "plugins/marketplaces/claude-plugins-official";
11301
+ var _testConfigDir, SYNC_DIRS, VALID_SYNC_MODES, _testDefaultClaudeDir, MARKETPLACE_REL = "plugins/marketplaces/claude-plugins-official";
11086
11302
  var init_config = __esm(() => {
11087
11303
  init_templates();
11088
11304
  init_json_file();
@@ -11091,6 +11307,7 @@ var init_config = __esm(() => {
11091
11307
  init_env();
11092
11308
  init_source();
11093
11309
  SYNC_DIRS = ["plugins", "skills"];
11310
+ VALID_SYNC_MODES = new Set(Object.values(SyncMode));
11094
11311
  });
11095
11312
 
11096
11313
  // node_modules/react/cjs/react.development.js
@@ -56073,15 +56290,10 @@ function useConfig() {
56073
56290
  return instance;
56074
56291
  }, [reload]);
56075
56292
  const toggleAutoSync = import_react66.useCallback(async (name, enable) => {
56076
- const instance = await getInstance(name);
56077
- if (!instance)
56078
- throw new ClaudeMultiError(ErrorCode.INSTANCE_NOT_FOUND, `Instance '${name}' not found`);
56079
- await updateInstanceAutoSync(name, enable);
56080
- if (enable) {
56081
- await syncPluginsAndSkills(instance.configDir);
56082
- } else {
56083
- await unsyncPluginsAndSkills(instance.configDir);
56084
- }
56293
+ await toggleSyncMode(name, enable ? SyncMode.Auto : SyncMode.FullManual);
56294
+ }, [reload]);
56295
+ const toggleSyncMode = import_react66.useCallback(async (name, newMode) => {
56296
+ await updateInstanceSyncMode(name, newMode);
56085
56297
  await reload();
56086
56298
  }, [reload]);
56087
56299
  const syncTemplateEnv = import_react66.useCallback(async (instance) => {
@@ -56106,11 +56318,14 @@ function useConfig() {
56106
56318
  addInstance: addInstance2,
56107
56319
  removeInstance: removeInstance2,
56108
56320
  toggleAutoSync,
56321
+ toggleSyncMode,
56109
56322
  syncTemplateEnv,
56110
56323
  regenerateWrapper,
56111
56324
  migrationStatus,
56112
56325
  instanceMigrationVersion: config?.instanceMigrationVersion,
56113
56326
  getInstance,
56327
+ getSyncMode,
56328
+ syncModeLabel,
56114
56329
  listInstances,
56115
56330
  hasDefaultConfig: hasDefaultClaudeConfig,
56116
56331
  hasDefaultMcpConfig,
@@ -56137,6 +56352,7 @@ function useConfig() {
56137
56352
  getApiKeyPlaceholder,
56138
56353
  updateInstanceAutoSync,
56139
56354
  syncPluginsAndSkills,
56355
+ halfSyncPluginsAndSkills,
56140
56356
  initializeInstanceState,
56141
56357
  mergeProviderEnv,
56142
56358
  listDefaultPlugins,
@@ -56145,6 +56361,7 @@ function useConfig() {
56145
56361
  copySinglePlugin,
56146
56362
  removeSinglePlugin,
56147
56363
  isPluginsSymlinked,
56364
+ isHalfManualSync,
56148
56365
  getMcpServersFromPlugins,
56149
56366
  getInstanceMcpServers,
56150
56367
  setCustomMcpServer,
@@ -56162,6 +56379,7 @@ var init_useConfig = __esm(() => {
56162
56379
  init_templates();
56163
56380
  init_migration();
56164
56381
  init_errors();
56382
+ init_constants();
56165
56383
  import_react66 = __toESM(require_react(), 1);
56166
56384
  });
56167
56385
 
@@ -56229,8 +56447,8 @@ function reducer5(state, action) {
56229
56447
  return { ...state, selectedRegion: action.region };
56230
56448
  case "SET_API_KEY":
56231
56449
  return { ...state, apiKey: action.key };
56232
- case "SET_AUTO_SYNC":
56233
- return { ...state, autoSync: action.value };
56450
+ case "SET_SYNC_MODE":
56451
+ return { ...state, syncMode: action.value };
56234
56452
  case "SET_COPY_OPTION":
56235
56453
  return { ...state, copyOption: action.value };
56236
56454
  case "SET_PLUGIN_IDS":
@@ -56252,7 +56470,7 @@ function initialState(initialName) {
56252
56470
  selectedProvider: null,
56253
56471
  selectedRegion: null,
56254
56472
  apiKey: "",
56255
- autoSync: false,
56473
+ syncMode: SyncMode.Auto,
56256
56474
  copyOption: CopyOption.None,
56257
56475
  selectedPluginIds: [],
56258
56476
  result: null
@@ -56438,23 +56656,40 @@ var import_react67, jsx_dev_runtime4, AddResult = ({
56438
56656
  onSubmit
56439
56657
  }, undefined, false, undefined, this)
56440
56658
  ]
56441
- }, undefined, true, undefined, this), AutosyncStep = ({ onConfirm }) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
56442
- flexDirection: "column",
56443
- gap: 1,
56444
- children: [
56445
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
56446
- children: "Auto-sync plugins and skills via symlinks?"
56447
- }, undefined, false, undefined, this),
56448
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
56449
- dimColor: true,
56450
- children: "Shares plugins/skills from ~/.claude across instances"
56451
- }, undefined, false, undefined, this),
56452
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(ConfirmInput, {
56453
- onConfirm: () => onConfirm(true),
56454
- onCancel: () => onConfirm(false)
56455
- }, undefined, false, undefined, this)
56456
- ]
56457
- }, undefined, true, undefined, this), AddInstance = ({ onBack, initialName }) => {
56659
+ }, undefined, true, undefined, this), SyncModeStep = ({ onSelect }) => {
56660
+ const syncModeOptions = [
56661
+ {
56662
+ label: "Auto-sync — symlink entire plugins/skills dirs (instant sync)",
56663
+ value: SyncMode.Auto
56664
+ },
56665
+ {
56666
+ label: "Half-manual — symlink individual items (new installs stay isolated)",
56667
+ value: SyncMode.HalfManual
56668
+ },
56669
+ {
56670
+ label: "Full-manual — copy everything (completely independent)",
56671
+ value: SyncMode.FullManual
56672
+ }
56673
+ ];
56674
+ return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
56675
+ flexDirection: "column",
56676
+ gap: 1,
56677
+ children: [
56678
+ /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
56679
+ children: "Choose how to sync plugins and skills:"
56680
+ }, undefined, false, undefined, this),
56681
+ /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
56682
+ dimColor: true,
56683
+ children: "Controls how ~/.claude content is shared with this instance"
56684
+ }, undefined, false, undefined, this),
56685
+ /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Select, {
56686
+ options: syncModeOptions,
56687
+ visibleOptionCount: syncModeOptions.length,
56688
+ onChange: (value) => onSelect(value)
56689
+ }, undefined, false, undefined, this)
56690
+ ]
56691
+ }, undefined, true, undefined, this);
56692
+ }, AddInstance = ({ onBack, initialName }) => {
56458
56693
  const { exit } = use_app_default();
56459
56694
  const cfg = useConfig();
56460
56695
  const [state, dispatch] = import_react67.useReducer(reducer5, initialName, initialState);
@@ -56540,7 +56775,8 @@ var import_react67, jsx_dev_runtime4, AddResult = ({
56540
56775
  configDir: cDir,
56541
56776
  binaryPath: bPath,
56542
56777
  createdAt: new Date().toISOString(),
56543
- autoSync: sync,
56778
+ autoSync: sync === SyncMode.Auto,
56779
+ syncMode: sync,
56544
56780
  createdWithVersion: getClaudeMultiVersion(),
56545
56781
  ...state.useProvider && state.selectedProvider ? { providerTemplate: state.selectedProvider } : {},
56546
56782
  ...state.selectedRegion && state.useProvider && state.selectedProvider && providerHasRegions(state.selectedProvider) ? { providerRegion: state.selectedRegion } : {}
@@ -56559,8 +56795,13 @@ var import_react67, jsx_dev_runtime4, AddResult = ({
56559
56795
  if (selections.length > 0) {
56560
56796
  await cfg.copySelectedPlugins(cDir, selections);
56561
56797
  }
56798
+ if (sync === SyncMode.Auto) {
56799
+ await cfg.syncPluginsAndSkills(cDir);
56800
+ } else if (sync === SyncMode.HalfManual) {
56801
+ await cfg.halfSyncPluginsAndSkills(cDir);
56802
+ }
56562
56803
  } else if (copyOpt === CopyOption.All) {
56563
- await cfg.copyAllFromDefault(cDir);
56804
+ await cfg.copyAllFromDefault(cDir, sync);
56564
56805
  } else if (copyOpt === CopyOption.Settings) {
56565
56806
  await cfg.copySettingsFromDefault(cDir);
56566
56807
  }
@@ -56588,7 +56829,7 @@ var import_react67, jsx_dev_runtime4, AddResult = ({
56588
56829
  } else if (value === CopyOption.All) {
56589
56830
  dispatch({ type: "SET_STEP", step: "autosync" /* Autosync */ });
56590
56831
  } else {
56591
- doCreate(value, false);
56832
+ doCreate(value, SyncMode.FullManual);
56592
56833
  }
56593
56834
  }, [doCreate]);
56594
56835
  const handlePluginSelection = import_react67.useCallback((ids) => {
@@ -56599,9 +56840,9 @@ var import_react67, jsx_dev_runtime4, AddResult = ({
56599
56840
  dispatch({ type: "SET_PLUGIN_IDS", ids });
56600
56841
  dispatch({ type: "SET_STEP", step: "autosync" /* Autosync */ });
56601
56842
  }, []);
56602
- const handleAutoSyncConfirm = import_react67.useCallback((confirmed) => {
56603
- dispatch({ type: "SET_AUTO_SYNC", value: confirmed });
56604
- doCreate(state.copyOption, confirmed);
56843
+ const handleSyncModeSelect = import_react67.useCallback((mode) => {
56844
+ dispatch({ type: "SET_SYNC_MODE", value: mode });
56845
+ doCreate(state.copyOption, mode);
56605
56846
  }, [state.copyOption, doCreate]);
56606
56847
  const hasDefaultConfig = cfg.hasDefaultConfig();
56607
56848
  const providerOptions = [
@@ -56674,7 +56915,7 @@ var import_react67, jsx_dev_runtime4, AddResult = ({
56674
56915
  children: "No default Claude config found. Starting fresh."
56675
56916
  }, undefined, false, undefined, this),
56676
56917
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(ConfirmInput, {
56677
- onConfirm: () => doCreate(CopyOption.None, state.autoSync),
56918
+ onConfirm: () => doCreate(CopyOption.None, SyncMode.FullManual),
56678
56919
  onCancel: onBack
56679
56920
  }, undefined, false, undefined, this)
56680
56921
  ]
@@ -56701,8 +56942,8 @@ var import_react67, jsx_dev_runtime4, AddResult = ({
56701
56942
  defaultPluginsLength: defaultPlugins.length,
56702
56943
  onSubmit: handlePluginSelection
56703
56944
  }, undefined, false, undefined, this),
56704
- state.step === "autosync" /* Autosync */ && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(AutosyncStep, {
56705
- onConfirm: handleAutoSyncConfirm
56945
+ state.step === "autosync" /* Autosync */ && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(SyncModeStep, {
56946
+ onSelect: handleSyncModeSelect
56706
56947
  }, undefined, false, undefined, this),
56707
56948
  state.step === "creating" /* Creating */ && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
56708
56949
  dimColor: true,
@@ -56749,7 +56990,7 @@ var init_AddInstance = __esm(async () => {
56749
56990
  ["paths-confirm" /* PathsConfirm */]: "Paths",
56750
56991
  ["copy-options" /* CopyOptions */]: "Copy Options",
56751
56992
  ["select-plugins" /* SelectPlugins */]: "Select Plugins",
56752
- ["autosync" /* Autosync */]: "Auto-Sync",
56993
+ ["autosync" /* Autosync */]: "Sync Mode",
56753
56994
  ["creating" /* Creating */]: "Creating...",
56754
56995
  ["done" /* Done */]: "Complete"
56755
56996
  };
@@ -57240,13 +57481,33 @@ var init_useHealthCheck = __esm(() => {
57240
57481
  });
57241
57482
 
57242
57483
  // src/ink/components/InstanceCard.tsx
57484
+ function syncModeIcon(mode) {
57485
+ switch (mode) {
57486
+ case SyncMode.Auto:
57487
+ return "⚡";
57488
+ case SyncMode.HalfManual:
57489
+ return "\uD83D\uDD17";
57490
+ case SyncMode.FullManual:
57491
+ return "\uD83D\uDCE6";
57492
+ }
57493
+ }
57494
+ function syncModeColor(mode) {
57495
+ switch (mode) {
57496
+ case SyncMode.Auto:
57497
+ return "green";
57498
+ case SyncMode.HalfManual:
57499
+ return "cyan";
57500
+ case SyncMode.FullManual:
57501
+ return "yellow";
57502
+ }
57503
+ }
57243
57504
  var jsx_dev_runtime7, InstanceCard = ({
57244
57505
  instance,
57245
57506
  index = 0
57246
57507
  }) => {
57247
- const autoSyncOn = instance.autoSync !== false;
57508
+ const mode = getSyncMode(instance);
57248
57509
  const visible = useFadeIn(index * 60 + 50);
57249
- const pulse = usePulse(autoSyncOn ? ["●", "◉", "●"] : ["○", "◌", "○"], 800);
57510
+ const pulse = usePulse(mode === SyncMode.Auto ? ["●", "◉", "●"] : mode === SyncMode.HalfManual ? ["◐", "◑", "◐"] : ["○", "◌", "○"], 800);
57250
57511
  if (!visible)
57251
57512
  return null;
57252
57513
  const verLabel = instance.createdWithVersion === LEGACY_INSTANCE_VERSION ? "before version tracking" : instance.createdWithVersion;
@@ -57259,8 +57520,8 @@ var jsx_dev_runtime7, InstanceCard = ({
57259
57520
  gap: 1,
57260
57521
  children: [
57261
57522
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
57262
- color: autoSyncOn ? "green" : "yellow",
57263
- children: autoSyncOn ? pulse : "○"
57523
+ color: syncModeColor(mode),
57524
+ children: pulse
57264
57525
  }, undefined, false, undefined, this),
57265
57526
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
57266
57527
  bold: true,
@@ -57268,8 +57529,12 @@ var jsx_dev_runtime7, InstanceCard = ({
57268
57529
  }, undefined, false, undefined, this),
57269
57530
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
57270
57531
  dimColor: true,
57271
- children: autoSyncOn ? "synced" : "manual"
57272
- }, undefined, false, undefined, this)
57532
+ children: [
57533
+ syncModeIcon(mode),
57534
+ " ",
57535
+ mode
57536
+ ]
57537
+ }, undefined, true, undefined, this)
57273
57538
  ]
57274
57539
  }, undefined, true, undefined, this),
57275
57540
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
@@ -57337,6 +57602,8 @@ var jsx_dev_runtime7, InstanceCard = ({
57337
57602
  };
57338
57603
  var init_InstanceCard = __esm(async () => {
57339
57604
  init_useAnimations();
57605
+ init_config();
57606
+ init_constants();
57340
57607
  init_migration();
57341
57608
  await init_build2();
57342
57609
  jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
@@ -57531,6 +57798,16 @@ var init_instance_diagnostics = __esm(() => {
57531
57798
  });
57532
57799
 
57533
57800
  // src/ink/screens/ShowInstanceInfo.tsx
57801
+ function syncModeColor2(mode) {
57802
+ switch (mode) {
57803
+ case SyncMode.Auto:
57804
+ return "green";
57805
+ case SyncMode.HalfManual:
57806
+ return "cyan";
57807
+ case SyncMode.FullManual:
57808
+ return "yellow";
57809
+ }
57810
+ }
57534
57811
  function templateStatusLabel(status) {
57535
57812
  switch (status) {
57536
57813
  case "match":
@@ -57588,7 +57865,7 @@ var import_react69, jsx_dev_runtime10, DetailRow = ({
57588
57865
  }, undefined, true, undefined, this);
57589
57866
  }, EMPTY_INFO, ACTION_VALUES, ShowInstanceInfo = ({ onBack }) => {
57590
57867
  const { exit } = use_app_default();
57591
- const { instances: instances2, listInstancePlugins: listInstancePlugins2, getInstanceMcpServers: getInstanceMcpServers2, syncTemplateEnv, regenerateWrapper } = useConfig();
57868
+ const { instances: instances2, listInstancePlugins: listInstancePlugins2, getInstanceMcpServers: getInstanceMcpServers2, syncTemplateEnv, regenerateWrapper, toggleSyncMode } = useConfig();
57592
57869
  const [step, setStep] = import_react69.useState("select");
57593
57870
  const [selected, setSelected] = import_react69.useState(null);
57594
57871
  const [infoData, setInfoData] = import_react69.useState(EMPTY_INFO);
@@ -57597,6 +57874,7 @@ var import_react69, jsx_dev_runtime10, DetailRow = ({
57597
57874
  const [providerName, setProviderName] = import_react69.useState(null);
57598
57875
  const [actionResult, setActionResult] = import_react69.useState(null);
57599
57876
  const [executing, setExecuting] = import_react69.useState(false);
57877
+ const [pendingSyncConvert, setPendingSyncConvert] = import_react69.useState(null);
57600
57878
  const liveSelected = selected ? instances2.find((i) => i.name === selected.name) ?? selected : null;
57601
57879
  const runDiagnostics = import_react69.useCallback((inst) => {
57602
57880
  const tmpl = detectTemplateMismatch(inst);
@@ -57687,9 +57965,31 @@ var import_react69, jsx_dev_runtime10, DetailRow = ({
57687
57965
  runDiagnostics(inst);
57688
57966
  await loadInstanceInfo(inst);
57689
57967
  };
57968
+ const VALID_SYNC_MODES2 = new Set(Object.values(SyncMode));
57690
57969
  const handleAction = async (value) => {
57691
57970
  if (executing)
57692
57971
  return;
57972
+ if (value.startsWith("convert:")) {
57973
+ const rawMode = value.replace("convert:", "");
57974
+ if (!VALID_SYNC_MODES2.has(rawMode)) {
57975
+ setActionResult({ message: `Invalid sync mode: ${rawMode}`, type: "error" });
57976
+ setStep("result");
57977
+ return;
57978
+ }
57979
+ const targetMode = rawMode;
57980
+ setPendingSyncConvert(targetMode);
57981
+ setStep("executing");
57982
+ setExecuting(true);
57983
+ try {
57984
+ await toggleSyncMode(selected.name, targetMode);
57985
+ setActionResult({ message: `Sync mode set to ${syncModeLabel(targetMode)} for '${selected.name}'`, type: "success" });
57986
+ } catch (err) {
57987
+ setActionResult({ message: err instanceof Error ? err.message : String(err), type: "error" });
57988
+ }
57989
+ setExecuting(false);
57990
+ setStep("result");
57991
+ return;
57992
+ }
57693
57993
  if (value === ACTION_VALUES.Back || !selected) {
57694
57994
  setStep("info");
57695
57995
  return;
@@ -57732,6 +58032,17 @@ var import_react69, jsx_dev_runtime10, DetailRow = ({
57732
58032
  value: ACTION_VALUES.OverrideWrapper
57733
58033
  });
57734
58034
  }
58035
+ const syncTarget = liveSelected ?? selected;
58036
+ if (syncTarget) {
58037
+ const currentMode = getSyncMode(syncTarget);
58038
+ const downgrades = availableSyncModeConversions(currentMode);
58039
+ for (const mode of downgrades) {
58040
+ options.push({
58041
+ label: `\uD83D\uDD04 Convert to ${syncModeLabel(mode)}`,
58042
+ value: `convert:${mode}`
58043
+ });
58044
+ }
58045
+ }
57735
58046
  options.push({ label: "← Back", value: ACTION_VALUES.Back });
57736
58047
  return options;
57737
58048
  };
@@ -57791,9 +58102,9 @@ var import_react69, jsx_dev_runtime10, DetailRow = ({
57791
58102
  delay: 150
57792
58103
  }, undefined, false, undefined, this),
57793
58104
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(DetailRow, {
57794
- label: "Auto-sync",
57795
- value: displayInstance.autoSync !== false ? "✓ Enabled" : "✗ Disabled",
57796
- color: displayInstance.autoSync !== false ? "green" : "red",
58105
+ label: "Sync mode",
58106
+ value: syncModeLabel(getSyncMode(displayInstance)),
58107
+ color: syncModeColor2(getSyncMode(displayInstance)),
57797
58108
  delay: 200
57798
58109
  }, undefined, false, undefined, this),
57799
58110
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(DetailRow, {
@@ -57964,6 +58275,8 @@ var init_ShowInstanceInfo = __esm(async () => {
57964
58275
  init_useConfig();
57965
58276
  init_useAnimations();
57966
58277
  init_migration();
58278
+ init_constants();
58279
+ init_config();
57967
58280
  init_instance_diagnostics();
57968
58281
  await __promiseAll([
57969
58282
  init_build2(),
@@ -57985,6 +58298,7 @@ var init_ShowInstanceInfo = __esm(async () => {
57985
58298
  SyncTemplate: "sync-template",
57986
58299
  UpdateWrapper: "update-wrapper",
57987
58300
  OverrideWrapper: "override-wrapper",
58301
+ ConvertSyncMode: "convert-sync-mode",
57988
58302
  Back: "back"
57989
58303
  };
57990
58304
  });
@@ -58254,32 +58568,69 @@ var init_useMessage = __esm(() => {
58254
58568
  });
58255
58569
 
58256
58570
  // src/ink/screens/ToggleAutoSync.tsx
58257
- var import_react72, jsx_dev_runtime12, ToggleActionValue, ToggleAction = ({ selected, actionOptions, onAction }) => {
58571
+ function syncModeColor3(mode) {
58572
+ switch (mode) {
58573
+ case SyncMode.Auto:
58574
+ return "green";
58575
+ case SyncMode.HalfManual:
58576
+ return "cyan";
58577
+ case SyncMode.FullManual:
58578
+ return "yellow";
58579
+ }
58580
+ }
58581
+ function syncModeIcon2(mode) {
58582
+ switch (mode) {
58583
+ case SyncMode.Auto:
58584
+ return "⚡";
58585
+ case SyncMode.HalfManual:
58586
+ return "\uD83D\uDD17";
58587
+ case SyncMode.FullManual:
58588
+ return "\uD83D\uDCE6";
58589
+ }
58590
+ }
58591
+ var import_react72, jsx_dev_runtime12, SyncModeAction = ({ selected, actionOptions, onAction }) => {
58258
58592
  const showStatus = useFadeIn(50);
58259
58593
  const showOptions = useFadeIn(150);
58594
+ const currentMode = getSyncMode(selected);
58260
58595
  return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
58261
58596
  flexDirection: "column",
58262
58597
  gap: 1,
58263
58598
  children: [
58264
58599
  showStatus && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
58265
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58266
- children: [
58267
- "Auto-sync for ",
58268
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58269
- bold: true,
58270
- color: "cyan",
58271
- children: selected.name
58272
- }, undefined, false, undefined, this),
58273
- " is currently ",
58274
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58275
- bold: true,
58276
- color: selected.autoSync !== false ? "green" : "red",
58277
- children: selected.autoSync !== false ? "ON" : "OFF"
58278
- }, undefined, false, undefined, this)
58279
- ]
58280
- }, undefined, true, undefined, this)
58281
- }, undefined, false, undefined, this),
58282
- showOptions && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(jsx_dev_runtime12.Fragment, {
58600
+ flexDirection: "column",
58601
+ gap: 0,
58602
+ children: [
58603
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58604
+ children: [
58605
+ "Sync mode for ",
58606
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58607
+ bold: true,
58608
+ color: "cyan",
58609
+ children: selected.name
58610
+ }, undefined, false, undefined, this),
58611
+ " is",
58612
+ " ",
58613
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58614
+ bold: true,
58615
+ color: syncModeColor3(currentMode),
58616
+ children: [
58617
+ syncModeIcon2(currentMode),
58618
+ " ",
58619
+ syncModeLabel(currentMode)
58620
+ ]
58621
+ }, undefined, true, undefined, this)
58622
+ ]
58623
+ }, undefined, true, undefined, this),
58624
+ currentMode !== SyncMode.FullManual && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58625
+ dimColor: true,
58626
+ children: [
58627
+ "Can convert to: ",
58628
+ availableSyncModeConversions(currentMode).map((m) => syncModeLabel(m)).join(", ")
58629
+ ]
58630
+ }, undefined, true, undefined, this)
58631
+ ]
58632
+ }, undefined, true, undefined, this),
58633
+ showOptions && actionOptions.length > 1 && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(jsx_dev_runtime12.Fragment, {
58283
58634
  children: [
58284
58635
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58285
58636
  children: "What would you like to do?"
@@ -58294,11 +58645,11 @@ var import_react72, jsx_dev_runtime12, ToggleActionValue, ToggleAction = ({ sele
58294
58645
  ]
58295
58646
  }, undefined, true, undefined, this);
58296
58647
  }, ToggleAutoSync = ({ onBack }) => {
58297
- const { instances: instances2, toggleAutoSync } = useConfig();
58648
+ const { instances: instances2, toggleSyncMode, syncPluginsAndSkills: syncPluginsAndSkills2, halfSyncPluginsAndSkills: halfSyncPluginsAndSkills2 } = useConfig();
58298
58649
  const [step, setStep] = import_react72.useState("select");
58299
58650
  const [selected, setSelected] = import_react72.useState(null);
58300
58651
  const { error, setError } = useMessage();
58301
- const [newStatus, setNewStatus] = import_react72.useState(false);
58652
+ const [newMode, setNewMode] = import_react72.useState(SyncMode.Auto);
58302
58653
  useNavigation(() => {
58303
58654
  if (step === "action") {
58304
58655
  setStep("select");
@@ -58317,7 +58668,7 @@ var import_react72, jsx_dev_runtime12, ToggleActionValue, ToggleAction = ({ sele
58317
58668
  paddingY: 1,
58318
58669
  children: [
58319
58670
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Header, {
58320
- title: "\uD83D\uDD04 Toggle Auto-Sync"
58671
+ title: "\uD83D\uDD04 Sync Mode"
58321
58672
  }, undefined, false, undefined, this),
58322
58673
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58323
58674
  color: "yellow",
@@ -58333,10 +58684,13 @@ var import_react72, jsx_dev_runtime12, ToggleActionValue, ToggleAction = ({ sele
58333
58684
  ]
58334
58685
  }, undefined, true, undefined, this);
58335
58686
  }
58336
- const instanceOptions = instances2.map((i) => ({
58337
- label: `${i.name} (${i.autoSync !== false ? "on" : "off"})`,
58338
- value: i.name
58339
- }));
58687
+ const instanceOptions = instances2.map((i) => {
58688
+ const mode = getSyncMode(i);
58689
+ return {
58690
+ label: `${i.name} (${syncModeIcon2(mode)} ${syncModeLabel(mode)})`,
58691
+ value: i.name
58692
+ };
58693
+ });
58340
58694
  const handleSelect = (value) => {
58341
58695
  const inst = instances2.find((i) => i.name === value);
58342
58696
  if (inst) {
@@ -58344,32 +58698,69 @@ var import_react72, jsx_dev_runtime12, ToggleActionValue, ToggleAction = ({ sele
58344
58698
  setStep("action");
58345
58699
  }
58346
58700
  };
58701
+ const VALID_MODES = new Set(Object.values(SyncMode));
58702
+ const FORCE_RESYNC = "force-resync";
58347
58703
  const handleAction = async (value) => {
58348
- if (value === ToggleActionValue.Cancel || !selected) {
58704
+ if (value === "cancel" || !selected) {
58349
58705
  setStep("select");
58350
58706
  setSelected(null);
58351
58707
  return;
58352
58708
  }
58353
- const currentStatus = selected.autoSync !== false;
58354
- const nextStatus = value === ToggleActionValue.ForceSync ? true : !currentStatus;
58709
+ if (value === FORCE_RESYNC) {
58710
+ setStep("syncing");
58711
+ try {
58712
+ const mode = getSyncMode(selected);
58713
+ if (mode === SyncMode.Auto) {
58714
+ await syncPluginsAndSkills2(selected.configDir);
58715
+ } else if (mode === SyncMode.HalfManual) {
58716
+ await halfSyncPluginsAndSkills2(selected.configDir);
58717
+ }
58718
+ setNewMode(mode);
58719
+ setStep("done");
58720
+ } catch (err) {
58721
+ setError(err instanceof Error ? err.message : String(err));
58722
+ setStep("select");
58723
+ }
58724
+ return;
58725
+ }
58726
+ if (!VALID_MODES.has(value)) {
58727
+ setError(`Invalid sync mode: ${value}`);
58728
+ setStep("select");
58729
+ return;
58730
+ }
58731
+ const targetMode = value;
58355
58732
  setStep("syncing");
58356
58733
  try {
58357
- await toggleAutoSync(selected.name, nextStatus);
58358
- setNewStatus(nextStatus);
58734
+ await toggleSyncMode(selected.name, targetMode);
58735
+ setNewMode(targetMode);
58359
58736
  setStep("done");
58360
58737
  } catch (err) {
58361
58738
  setError(err instanceof Error ? err.message : String(err));
58362
58739
  setStep("select");
58363
58740
  }
58364
58741
  };
58365
- const actionOptions = [
58366
- {
58367
- label: selected?.autoSync !== false ? "Turn off (copy files locally)" : "Turn on (use symlinks)",
58368
- value: ToggleActionValue.Toggle
58369
- },
58370
- ...selected?.autoSync !== false ? [{ label: "Force re-sync (rebuild symlinks)", value: ToggleActionValue.ForceSync }] : [],
58371
- { label: "Cancel", value: ToggleActionValue.Cancel }
58372
- ];
58742
+ const buildActionOptions = () => {
58743
+ if (!selected)
58744
+ return [{ label: "Cancel", value: "cancel" }];
58745
+ const currentMode = getSyncMode(selected);
58746
+ const options = [];
58747
+ if (currentMode === SyncMode.Auto || currentMode === SyncMode.HalfManual) {
58748
+ options.push({ label: "⚡ Force re-sync (rebuild symlinks)", value: FORCE_RESYNC });
58749
+ }
58750
+ const downgrades = availableSyncModeConversions(currentMode);
58751
+ for (const mode of downgrades) {
58752
+ options.push({
58753
+ label: `${syncModeIcon2(mode)} Convert to ${syncModeLabel(mode)}`,
58754
+ value: mode
58755
+ });
58756
+ }
58757
+ if (options.length === 0) {
58758
+ options.push({ label: "(already at most isolated mode)", value: "cancel" });
58759
+ }
58760
+ options.push({ label: "Cancel", value: "cancel" });
58761
+ return options;
58762
+ };
58763
+ const actionOptions = buildActionOptions();
58373
58764
  return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
58374
58765
  flexDirection: "column",
58375
58766
  width: "100",
@@ -58377,7 +58768,7 @@ var import_react72, jsx_dev_runtime12, ToggleActionValue, ToggleAction = ({ sele
58377
58768
  paddingY: 1,
58378
58769
  children: [
58379
58770
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Header, {
58380
- title: "\uD83D\uDD04 Toggle Auto-Sync"
58771
+ title: "\uD83D\uDD04 Sync Mode"
58381
58772
  }, undefined, false, undefined, this),
58382
58773
  error && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(StatusBar, {
58383
58774
  message: error,
@@ -58397,17 +58788,17 @@ var import_react72, jsx_dev_runtime12, ToggleActionValue, ToggleAction = ({ sele
58397
58788
  }, undefined, false, undefined, this)
58398
58789
  ]
58399
58790
  }, undefined, true, undefined, this),
58400
- step === "action" && selected && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(ToggleAction, {
58791
+ step === "action" && selected && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(SyncModeAction, {
58401
58792
  selected,
58402
58793
  actionOptions,
58403
58794
  onAction: handleAction
58404
58795
  }, undefined, false, undefined, this),
58405
58796
  step === "syncing" && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text, {
58406
58797
  dimColor: true,
58407
- children: "Syncing…"
58798
+ children: "Converting sync mode…"
58408
58799
  }, undefined, false, undefined, this),
58409
58800
  step === "done" && selected && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(StatusBar, {
58410
- message: `Auto-sync ${newStatus ? "enabled" : "disabled"} for '${selected.name}'`,
58801
+ message: `Sync mode set to ${syncModeIcon2(newMode)} ${syncModeLabel(newMode)} for '${selected.name}'`,
58411
58802
  type: "success"
58412
58803
  }, undefined, false, undefined, this),
58413
58804
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
@@ -58424,6 +58815,8 @@ var init_ToggleAutoSync = __esm(async () => {
58424
58815
  init_useConfig();
58425
58816
  init_useMessage();
58426
58817
  init_useAnimations();
58818
+ init_constants();
58819
+ init_config();
58427
58820
  await __promiseAll([
58428
58821
  init_build2(),
58429
58822
  init_build3(),
@@ -58433,11 +58826,6 @@ var init_ToggleAutoSync = __esm(async () => {
58433
58826
  ]);
58434
58827
  import_react72 = __toESM(require_react(), 1);
58435
58828
  jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
58436
- ToggleActionValue = {
58437
- Toggle: "toggle",
58438
- ForceSync: "force-sync",
58439
- Cancel: "cancel"
58440
- };
58441
58829
  });
58442
58830
 
58443
58831
  // src/ink/screens/ManagePlugins.tsx
@@ -58486,7 +58874,8 @@ var import_react73, jsx_dev_runtime13, PluginSuccess = ({ message }) => {
58486
58874
  removeSinglePlugin: removeSinglePlugin2,
58487
58875
  enablePlugin: enablePlugin2,
58488
58876
  disablePlugin: disablePlugin2,
58489
- isPluginsSymlinked: isPluginsSymlinked2
58877
+ isPluginsSymlinked: isPluginsSymlinked2,
58878
+ isHalfManualSync: isHalfManualSync2
58490
58879
  } = useConfig();
58491
58880
  const [step, setStep] = import_react73.useState("action");
58492
58881
  const [action, setAction] = import_react73.useState(null);
@@ -58547,7 +58936,7 @@ var import_react73, jsx_dev_runtime13, PluginSuccess = ({ message }) => {
58547
58936
  return;
58548
58937
  const modifiesPlugins = action === PluginAction.Install || action === PluginAction.Remove || action === PluginAction.Enable || action === PluginAction.Disable;
58549
58938
  if (modifiesPlugins) {
58550
- if (isPluginsSymlinked2(inst.configDir)) {
58939
+ if (isPluginsSymlinked2(inst.configDir) || isHalfManualSync2(inst.configDir)) {
58551
58940
  setStep("symlink-warning");
58552
58941
  return;
58553
58942
  }
@@ -58667,16 +59056,16 @@ var import_react73, jsx_dev_runtime13, PluginSuccess = ({ message }) => {
58667
59056
  gap: 1,
58668
59057
  children: [
58669
59058
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(StatusBar, {
58670
- message: `Instance '${selectedInstance}' has auto-sync enabled (symlinked plugins).`,
59059
+ message: `Instance '${selectedInstance}' has symlinked plugins (sync mode).`,
58671
59060
  type: "error"
58672
59061
  }, undefined, false, undefined, this),
58673
59062
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
58674
59063
  dimColor: true,
58675
- children: "Disable auto-sync first to manage plugins individually."
59064
+ children: "Switch to full-manual mode first to manage plugins individually."
58676
59065
  }, undefined, false, undefined, this),
58677
59066
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
58678
59067
  dimColor: true,
58679
- children: 'Use "\uD83D\uDD04 Toggle auto-sync" from the main menu.'
59068
+ children: 'Use "\uD83D\uDD04 Sync mode" from the main menu.'
58680
59069
  }, undefined, false, undefined, this),
58681
59070
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
58682
59071
  marginTop: 1,
@@ -58812,7 +59201,7 @@ var import_react74, jsx_dev_runtime14, SymlinkResults = ({ results }) => {
58812
59201
  ]
58813
59202
  }, undefined, true, undefined, this);
58814
59203
  }, FixSymlinks = ({ onBack }) => {
58815
- const { instances: instances2, detectBrokenSymlinks: detectBrokenSymlinks2, syncPluginsAndSkills: syncPluginsAndSkills2 } = useConfig();
59204
+ const { instances: instances2, detectBrokenSymlinks: detectBrokenSymlinks2, syncPluginsAndSkills: syncPluginsAndSkills2, halfSyncPluginsAndSkills: halfSyncPluginsAndSkills2 } = useConfig();
58816
59205
  const [step, setStep] = import_react74.useState("select");
58817
59206
  const [results, setResults] = import_react74.useState([]);
58818
59207
  const { error, setError } = useMessage();
@@ -58846,10 +59235,13 @@ var import_react74, jsx_dev_runtime14, SymlinkResults = ({ results }) => {
58846
59235
  ]
58847
59236
  }, undefined, true, undefined, this);
58848
59237
  }
58849
- const instanceOptions = instances2.map((i) => ({
58850
- label: `${i.name} ${i.autoSync !== false ? "(auto-sync)" : "(manual)"}`,
58851
- value: i.name
58852
- }));
59238
+ const instanceOptions = instances2.map((i) => {
59239
+ const mode = getSyncMode(i);
59240
+ return {
59241
+ label: `${i.name} (${mode})`,
59242
+ value: i.name
59243
+ };
59244
+ });
58853
59245
  const handleSelect = async (selectedNames) => {
58854
59246
  setStep("fixing");
58855
59247
  setError("");
@@ -58860,15 +59252,20 @@ var import_react74, jsx_dev_runtime14, SymlinkResults = ({ results }) => {
58860
59252
  return null;
58861
59253
  const diagnosis = detectBrokenSymlinks2(instance.configDir);
58862
59254
  const hasBroken = diagnosis.broken.length > 0;
58863
- if (hasBroken && instance.autoSync !== false) {
59255
+ const mode = getSyncMode(instance);
59256
+ if (hasBroken) {
58864
59257
  try {
58865
- await syncPluginsAndSkills2(instance.configDir);
59258
+ if (mode === SyncMode.Auto) {
59259
+ await syncPluginsAndSkills2(instance.configDir);
59260
+ } else if (mode === SyncMode.HalfManual) {
59261
+ await halfSyncPluginsAndSkills2(instance.configDir);
59262
+ }
58866
59263
  return { name, broken: diagnosis.broken, all: diagnosis.all, fixed: true };
58867
59264
  } catch {
58868
59265
  return { name, broken: diagnosis.broken, all: diagnosis.all, fixed: false };
58869
59266
  }
58870
59267
  } else {
58871
- return { name, broken: diagnosis.broken, all: diagnosis.all, fixed: !hasBroken };
59268
+ return { name, broken: diagnosis.broken, all: diagnosis.all, fixed: true };
58872
59269
  }
58873
59270
  }));
58874
59271
  setResults(fixResults.filter(Boolean));
@@ -58922,6 +59319,8 @@ var init_FixSymlinks = __esm(async () => {
58922
59319
  init_useConfig();
58923
59320
  init_useMessage();
58924
59321
  init_useAnimations();
59322
+ init_constants();
59323
+ init_config();
58925
59324
  await __promiseAll([
58926
59325
  init_build2(),
58927
59326
  init_build3(),
@@ -60167,7 +60566,7 @@ var import_react78, jsx_dev_runtime18, GoodbyeScreen = () => {
60167
60566
  ...instances2.length > 0 ? [
60168
60567
  { label: "ℹ️ Instance details", value: "info" },
60169
60568
  { label: "\uD83D\uDD0C Manage plugins", value: "plugins" },
60170
- { label: "\uD83D\uDD04 Toggle auto-sync", value: "autosync" },
60569
+ { label: "\uD83D\uDD04 Sync mode", value: "autosync" },
60171
60570
  { label: "\uD83D\uDD17 Re-sync symlinks", value: "resync" },
60172
60571
  { label: "\uD83D\uDDD1️ Remove instance", value: "remove" },
60173
60572
  { label: "⚙️ MCP servers", value: "mcp" }
@@ -60312,7 +60711,7 @@ function requireNonEmptyArgs(items, errorMsg, usage) {
60312
60711
  }
60313
60712
  var program2 = new Command;
60314
60713
  program2.name("claude-multi").description("Manage multiple Claude Code instances with different aliases").version(getClaudeMultiVersion());
60315
- program2.command("add <name>").description("Add a new Claude Code instance").option("-c, --config <dir>", "Config directory path", (name) => join9(homedir4(), `.claude-${name}`)).option("-b, --binary <path>", "Binary path (default: ~/.local/bin/claude-<name>)").option("--copy-settings", "Copy settings.json from default Claude").option("--copy-all", "Copy all files from default Claude").option("--copy-mcp", "Copy MCP server configurations from default Claude").option("--skip-prompts", "Skip interactive prompts (start fresh)").option("--provider <name>", "Use a provider template (glm, minimax, deepseek, mimo, mimo-token, kimi, qwen, qwen-coding)").option("--api-key <key>", "API key for the provider").option("--region <region>", "Region for regional providers (e.g., cn, sgp, ams for mimo-token)").option("--auto-sync", "Auto-sync plugins/skills via symlinks (default)").option("--manual", "Manually manage plugins/skills (copy files)").action(async (name, options) => {
60714
+ program2.command("add <name>").description("Add a new Claude Code instance").option("-c, --config <dir>", "Config directory path", (name) => join9(homedir4(), `.claude-${name}`)).option("-b, --binary <path>", "Binary path (default: ~/.local/bin/claude-<name>)").option("--copy-settings", "Copy settings.json from default Claude").option("--copy-all", "Copy all files from default Claude").option("--copy-mcp", "Copy MCP server configurations from default Claude").option("--skip-prompts", "Skip interactive prompts (start fresh)").option("--provider <name>", "Use a provider template (glm, minimax, deepseek, mimo, mimo-token, kimi, qwen, qwen-coding)").option("--api-key <key>", "API key for the provider").option("--region <region>", "Region for regional providers (e.g., cn, sgp, ams for mimo-token)").option("--auto-sync", "Auto-sync plugins/skills via symlinks (default)").option("--manual", "Manually manage plugins/skills (full copy)").option("--half-manual", "Symlink individual plugins/skills, new installs stay isolated").option("--sync-mode <mode>", "Sync mode: auto, half-manual, full-manual").action(async (name, options) => {
60316
60715
  try {
60317
60716
  const configDir = options.config || join9(homedir4(), `.claude-${name}`);
60318
60717
  const binaryPath = options.binary || getDefaultBinaryPath(name);
@@ -60324,7 +60723,29 @@ program2.command("add <name>").description("Add a new Claude Code instance").opt
60324
60723
  let useProviderTemplate = false;
60325
60724
  let providerTemplate = null;
60326
60725
  let apiKey = "";
60327
- let autoSync = !options.manual;
60726
+ let effectiveSyncMode;
60727
+ const flagCount = [options.syncMode, options.autoSync, options.halfManual, options.manual].filter(Boolean).length;
60728
+ if (flagCount > 1) {
60729
+ console.error(source_default.red("✗ Conflicting sync mode flags. Use only one of: --sync-mode, --auto-sync, --half-manual, --manual"));
60730
+ exitWithCode(1);
60731
+ }
60732
+ if (options.syncMode) {
60733
+ const valid = [SyncMode.Auto, SyncMode.HalfManual, SyncMode.FullManual];
60734
+ if (!valid.includes(options.syncMode)) {
60735
+ console.error(source_default.red(`✗ Invalid sync mode '${options.syncMode}'. Use: ${valid.join(", ")}`));
60736
+ exitWithCode(1);
60737
+ }
60738
+ effectiveSyncMode = options.syncMode;
60739
+ } else if (options.autoSync) {
60740
+ effectiveSyncMode = SyncMode.Auto;
60741
+ } else if (options.halfManual) {
60742
+ effectiveSyncMode = SyncMode.HalfManual;
60743
+ } else if (options.manual) {
60744
+ effectiveSyncMode = SyncMode.FullManual;
60745
+ } else {
60746
+ effectiveSyncMode = SyncMode.Auto;
60747
+ }
60748
+ const autoSync = effectiveSyncMode === SyncMode.Auto;
60328
60749
  let providerRegion;
60329
60750
  if (options.provider) {
60330
60751
  providerTemplate = getProviderTemplate(options.provider);
@@ -60417,6 +60838,7 @@ Found existing Claude Code configuration at ~/.claude`));
60417
60838
  binaryPath,
60418
60839
  createdAt: new Date().toISOString(),
60419
60840
  autoSync,
60841
+ syncMode: effectiveSyncMode,
60420
60842
  createdWithVersion: getClaudeMultiVersion(),
60421
60843
  ...useProviderTemplate && providerTemplate ? { providerTemplate: providerTemplate.name } : {},
60422
60844
  ...providerRegion ? { providerRegion } : {}
@@ -60438,11 +60860,13 @@ Found existing Claude Code configuration at ~/.claude`));
60438
60860
  }
60439
60861
  }
60440
60862
  if (copyAllFiles) {
60441
- await copyAllFromDefault(configDir);
60442
- if (autoSync) {
60863
+ await copyAllFromDefault(configDir, effectiveSyncMode);
60864
+ if (effectiveSyncMode === SyncMode.Auto) {
60443
60865
  console.log(source_default.green("✓ Copied all files with auto-sync (plugins/skills symlinked)"));
60866
+ } else if (effectiveSyncMode === SyncMode.HalfManual) {
60867
+ console.log(source_default.green("✓ Copied all files with half-manual sync (individual plugins/skills symlinked)"));
60444
60868
  } else {
60445
- console.log(source_default.green("✓ Copied all files from default Claude (manual mode)"));
60869
+ console.log(source_default.green("✓ Copied all files from default Claude (full-manual mode)"));
60446
60870
  }
60447
60871
  }
60448
60872
  if (useProviderTemplate && providerTemplate) {
@@ -60519,8 +60943,10 @@ program2.command("list").alias("ls").description("List all Claude Code instances
60519
60943
  const region = instance.providerRegion ? ` (${instance.providerRegion})` : "";
60520
60944
  console.log(source_default.gray(` Provider: ${instance.providerTemplate}${region}`));
60521
60945
  }
60522
- const autoSyncStatus = instance.autoSync !== false ? source_default.green("on") : source_default.yellow("off");
60523
- console.log(source_default.gray(` Auto-sync: ${autoSyncStatus}`));
60946
+ const mode = getSyncMode(instance);
60947
+ const label = syncModeLabel(mode);
60948
+ const modeColor = mode === SyncMode.Auto ? source_default.green : mode === SyncMode.HalfManual ? source_default.cyan : source_default.yellow;
60949
+ console.log(source_default.gray(` Sync mode: ${modeColor(label)}`));
60524
60950
  console.log(source_default.gray(` Version: ${formatVersionLabel(instance.createdWithVersion)}`));
60525
60951
  console.log();
60526
60952
  }
@@ -60537,8 +60963,9 @@ program2.command("info <name>").description("Show details about a specific insta
60537
60963
  console.log(`${source_default.gray("Binary:")} ${instance.binaryPath}`);
60538
60964
  console.log(`${source_default.gray("Config:")} ${instance.configDir}`);
60539
60965
  console.log(`${source_default.gray("Created:")} ${new Date(instance.createdAt).toLocaleString()}`);
60540
- const autoSyncStatus = instance.autoSync !== false ? source_default.green("✓ Enabled") : source_default.yellow("✗ Disabled");
60541
- console.log(`${source_default.gray("Auto-sync:")} ${autoSyncStatus}`);
60966
+ const mode = getSyncMode(instance);
60967
+ const modeColor = mode === SyncMode.Auto ? source_default.green : mode === SyncMode.HalfManual ? source_default.cyan : source_default.yellow;
60968
+ console.log(`${source_default.gray("Sync mode:")} ${modeColor(syncModeLabel(mode))}`);
60542
60969
  console.log(`${source_default.gray("Version:")} ${formatVersionLabel(instance.createdWithVersion)}`);
60543
60970
  if (instance.providerTemplate) {
60544
60971
  const region = instance.providerRegion ? ` (${instance.providerRegion})` : "";
@@ -60585,27 +61012,36 @@ program2.command("update").description("Update @anthropic-ai/claude-code to the
60585
61012
  exitWithCode(1);
60586
61013
  }
60587
61014
  });
60588
- program2.command("auto-sync <name> <status>").description("Toggle auto-sync for plugins/skills (on/off)").action(async (name, status) => {
61015
+ program2.command("auto-sync <name> <status>").description("Set sync mode for plugins/skills (auto, half-manual, full-manual, on, off)").action(async (name, status) => {
60589
61016
  try {
60590
61017
  const instance = await requireInstance(name);
61018
+ const currentMode = getSyncMode(instance);
60591
61019
  const normalized = status.toLowerCase();
60592
- const newStatus = normalized === "on" || normalized === "true" || status === "1";
60593
- const currentStatus = instance.autoSync !== false;
60594
- if (currentStatus === newStatus) {
60595
- console.log(source_default.yellow(`Auto-sync is already ${newStatus ? "enabled" : "disabled"} for '${name}'`));
61020
+ let newMode;
61021
+ if (normalized === "on" || normalized === "true" || status === "1") {
61022
+ newMode = SyncMode.Auto;
61023
+ } else if (normalized === "off" || normalized === "false" || status === "0") {
61024
+ newMode = SyncMode.FullManual;
61025
+ } else if (normalized === SyncMode.Auto || normalized === SyncMode.HalfManual || normalized === SyncMode.FullManual) {
61026
+ newMode = normalized;
61027
+ } else {
61028
+ console.error(source_default.red(`✗ Unknown sync mode '${status}'. Use: auto, half-manual, full-manual, on, off`));
61029
+ exitWithCode(1);
61030
+ }
61031
+ if (currentMode === newMode) {
61032
+ console.log(source_default.yellow(`Sync mode is already '${syncModeLabel(newMode)}' for '${name}'`));
60596
61033
  return;
60597
61034
  }
60598
- await updateInstanceAutoSync(name, newStatus);
61035
+ if (!canConvertSyncMode(currentMode, newMode)) {
61036
+ console.error(source_default.red(`✗ Cannot convert from '${syncModeLabel(currentMode)}' to '${syncModeLabel(newMode)}'. Only downgrades are allowed (auto → half-manual → full-manual).`));
61037
+ exitWithCode(1);
61038
+ }
60599
61039
  console.log(source_default.bold(`
60600
- \uD83D\uDD04 ${newStatus ? "Enabling" : "Disabling"} auto-sync for '${name}'...
61040
+ \uD83D\uDD04 Converting '${name}' from ${syncModeLabel(currentMode)} → ${syncModeLabel(newMode)}...
60601
61041
  `));
60602
- if (newStatus) {
60603
- await syncPluginsAndSkills(instance.configDir);
60604
- } else {
60605
- await unsyncPluginsAndSkills(instance.configDir);
60606
- }
61042
+ await updateInstanceSyncMode(name, newMode);
60607
61043
  console.log(source_default.green(`
60608
- ✓ Auto-sync ${newStatus ? "enabled" : "disabled"} for '${name}'`));
61044
+ ✓ Sync mode set to '${syncModeLabel(newMode)}' for '${name}'`));
60609
61045
  } catch (error) {
60610
61046
  console.error(source_default.red(`✗ Error: ${toMessage(error)}`));
60611
61047
  exitWithCode(1);
@@ -60855,6 +61291,10 @@ async function handlePluginsInstall(instanceName, pluginIds) {
60855
61291
  console.error(source_default.red("✗ Instance has auto-sync enabled (symlinked plugins). Disable auto-sync first."));
60856
61292
  exitWithCode(1);
60857
61293
  }
61294
+ if (isHalfManualSync(instance.configDir)) {
61295
+ console.error(source_default.red("✗ Instance has half-manual sync (individually symlinked plugins). Switch to full-manual first."));
61296
+ exitWithCode(1);
61297
+ }
60858
61298
  if (pluginIds.length === 0) {
60859
61299
  console.error(source_default.red("✗ No plugins specified"));
60860
61300
  console.log(source_default.gray("Usage: claude-multi plugins install <instance> <plugin-id>..."));
@@ -60889,6 +61329,10 @@ async function handlePluginsRemove(instanceName, pluginIds) {
60889
61329
  console.error(source_default.red("✗ Instance has auto-sync enabled (symlinked plugins). Disable auto-sync first."));
60890
61330
  exitWithCode(1);
60891
61331
  }
61332
+ if (isHalfManualSync(instance.configDir)) {
61333
+ console.error(source_default.red("✗ Instance has half-manual sync (individually symlinked plugins). Switch to full-manual first."));
61334
+ exitWithCode(1);
61335
+ }
60892
61336
  requireNonEmptyArgs(pluginIds, "✗ No plugins specified", "Usage: claude-multi plugins remove <instance> <plugin-id>...");
60893
61337
  const installed = listInstancePlugins(instance.configDir);
60894
61338
  const installedMap = new Map(installed.map((ip) => [ip.id, ip]));
@@ -61007,7 +61451,7 @@ async function handleFixSymlinks(names, fixAll) {
61007
61451
  name: "selected",
61008
61452
  message: "Select instances to fix:",
61009
61453
  choices: instances2.map((i) => ({
61010
- title: `${i.name} ${i.autoSync ? "(auto-sync)" : "(manual)"}`,
61454
+ title: `${i.name} (${getSyncMode(i)})`,
61011
61455
  value: i.name
61012
61456
  }))
61013
61457
  });
@@ -61019,28 +61463,38 @@ async function handleFixSymlinks(names, fixAll) {
61019
61463
  const fixResults = await Promise.all(instancesToFix.map(async (instance) => {
61020
61464
  const diagnosis = detectBrokenSymlinks(instance.configDir);
61021
61465
  const needsFix = diagnosis.broken.length > 0;
61466
+ const mode = getSyncMode(instance);
61022
61467
  let fixed = false;
61023
- if (needsFix && instance.autoSync) {
61024
- await syncPluginsAndSkills(instance.configDir);
61025
- fixed = true;
61468
+ if (needsFix) {
61469
+ try {
61470
+ if (mode === SyncMode.Auto) {
61471
+ await syncPluginsAndSkills(instance.configDir);
61472
+ fixed = true;
61473
+ } else if (mode === SyncMode.HalfManual) {
61474
+ await halfSyncPluginsAndSkills(instance.configDir);
61475
+ fixed = true;
61476
+ }
61477
+ } catch {}
61026
61478
  }
61027
- return { instance, diagnosis, needsFix, fixed };
61479
+ return { instance, diagnosis, needsFix, fixed, mode };
61028
61480
  }));
61029
- for (const { instance, diagnosis, needsFix } of fixResults) {
61481
+ for (const { instance, diagnosis, needsFix, fixed, mode } of fixResults) {
61030
61482
  console.log(source_default.bold(`
61031
61483
  \uD83D\uDD0D ${instance.name}`));
61032
61484
  console.log(` Config: ${instance.configDir}`);
61033
- console.log(` Auto-sync: ${instance.autoSync ? "enabled" : "disabled"}`);
61485
+ console.log(` Sync mode: ${syncModeLabel(mode)}`);
61034
61486
  if (diagnosis.all.length === 0) {
61035
- console.log(source_default.gray(" No symlinks found (manual mode)"));
61487
+ console.log(source_default.gray(" No symlinks found (full-manual mode)"));
61036
61488
  continue;
61037
61489
  }
61038
61490
  if (needsFix) {
61039
61491
  console.log(source_default.red(` ❌ Broken: ${diagnosis.broken.join(", ")}`));
61040
- if (instance.autoSync) {
61492
+ if (fixed) {
61041
61493
  console.log(source_default.green(` ✅ Fixed: ${diagnosis.broken.join(", ")}`));
61494
+ } else if (mode === SyncMode.FullManual) {
61495
+ console.log(source_default.yellow(" ⚠ Full-manual mode — no symlinks to repair."));
61042
61496
  } else {
61043
- console.log(source_default.yellow(" ⚠ Auto-sync is disabled. Enable it first."));
61497
+ console.log(source_default.yellow(" ⚠ Could not auto-repair. Try re-syncing via 'claude-multi auto-sync'."));
61044
61498
  }
61045
61499
  } else {
61046
61500
  console.log(source_default.green(` ✅ All symlinks OK: ${diagnosis.all.join(", ")}`));