claude-multi 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env node
2
- import { createRequire } from "node:module";
1
+ #!/usr/bin/env bun
2
+ // @bun
3
3
  var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
@@ -17,7 +17,7 @@ var __toESM = (mod, isNodeMode, target) => {
17
17
  return to;
18
18
  };
19
19
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
+ var __require = import.meta.require;
21
21
 
22
22
  // node_modules/commander/lib/error.js
23
23
  var require_error = __commonJS((exports) => {
@@ -342,7 +342,7 @@ var require_help = __commonJS((exports) => {
342
342
  return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
343
343
  }
344
344
  wrap(str, width, indent, minColumnWidth = 40) {
345
- const indents = " \\f\\t\\v   -    \uFEFF";
345
+ const indents = " \\f\\t\\v\xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF";
346
346
  const manualIndent = new RegExp(`[\\n][${indents}]+`);
347
347
  if (str.match(manualIndent))
348
348
  return str;
@@ -354,7 +354,7 @@ var require_help = __commonJS((exports) => {
354
354
  `, `
355
355
  `);
356
356
  const indentString = " ".repeat(indent);
357
- const zeroWidthSpace = "​";
357
+ const zeroWidthSpace = "\u200B";
358
358
  const breaks = `\\s${zeroWidthSpace}`;
359
359
  const regex = new RegExp(`
360
360
  |.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`, "g");
@@ -597,11 +597,11 @@ var require_suggestSimilar = __commonJS((exports) => {
597
597
 
598
598
  // node_modules/commander/lib/command.js
599
599
  var require_command = __commonJS((exports) => {
600
- var EventEmitter = __require("node:events").EventEmitter;
601
- var childProcess = __require("node:child_process");
602
- var path = __require("node:path");
603
- var fs = __require("node:fs");
604
- var process2 = __require("node:process");
600
+ var EventEmitter = __require("events").EventEmitter;
601
+ var childProcess = __require("child_process");
602
+ var path = __require("path");
603
+ var fs = __require("fs");
604
+ var process2 = __require("process");
605
605
  var { Argument, humanReadableArgName } = require_argument();
606
606
  var { CommanderError } = require_error();
607
607
  var { Help } = require_help();
@@ -2162,18 +2162,18 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
2162
2162
  // node_modules/prompts/dist/util/figures.js
2163
2163
  var require_figures = __commonJS((exports, module) => {
2164
2164
  var main = {
2165
- arrowUp: "↑",
2166
- arrowDown: "↓",
2167
- arrowLeft: "←",
2168
- arrowRight: "→",
2169
- radioOn: "◉",
2170
- radioOff: "◯",
2171
- tick: "✔",
2172
- cross: "✖",
2173
- ellipsis: "…",
2174
- pointerSmall: "›",
2175
- line: "─",
2176
- pointer: "❯"
2165
+ arrowUp: "\u2191",
2166
+ arrowDown: "\u2193",
2167
+ arrowLeft: "\u2190",
2168
+ arrowRight: "\u2192",
2169
+ radioOn: "\u25C9",
2170
+ radioOff: "\u25EF",
2171
+ tick: "\u2714",
2172
+ cross: "\u2716",
2173
+ ellipsis: "\u2026",
2174
+ pointerSmall: "\u203A",
2175
+ line: "\u2500",
2176
+ pointer: "\u276F"
2177
2177
  };
2178
2178
  var win = {
2179
2179
  arrowUp: main.arrowUp,
@@ -2182,11 +2182,11 @@ var require_figures = __commonJS((exports, module) => {
2182
2182
  arrowRight: main.arrowRight,
2183
2183
  radioOn: "(*)",
2184
2184
  radioOff: "( )",
2185
- tick: "√",
2186
- cross: "×",
2185
+ tick: "\u221A",
2186
+ cross: "\xD7",
2187
2187
  ellipsis: "...",
2188
- pointerSmall: "»",
2189
- line: "─",
2188
+ pointerSmall: "\xBB",
2189
+ line: "\u2500",
2190
2190
  pointer: ">"
2191
2191
  };
2192
2192
  var figures = process.platform === "win32" ? win : main;
@@ -4686,18 +4686,18 @@ var require_clear2 = __commonJS((exports, module) => {
4686
4686
  // node_modules/prompts/lib/util/figures.js
4687
4687
  var require_figures2 = __commonJS((exports, module) => {
4688
4688
  var main = {
4689
- arrowUp: "↑",
4690
- arrowDown: "↓",
4691
- arrowLeft: "←",
4692
- arrowRight: "→",
4693
- radioOn: "◉",
4694
- radioOff: "◯",
4695
- tick: "✔",
4696
- cross: "✖",
4697
- ellipsis: "…",
4698
- pointerSmall: "›",
4699
- line: "─",
4700
- pointer: "❯"
4689
+ arrowUp: "\u2191",
4690
+ arrowDown: "\u2193",
4691
+ arrowLeft: "\u2190",
4692
+ arrowRight: "\u2192",
4693
+ radioOn: "\u25C9",
4694
+ radioOff: "\u25EF",
4695
+ tick: "\u2714",
4696
+ cross: "\u2716",
4697
+ ellipsis: "\u2026",
4698
+ pointerSmall: "\u203A",
4699
+ line: "\u2500",
4700
+ pointer: "\u276F"
4701
4701
  };
4702
4702
  var win = {
4703
4703
  arrowUp: main.arrowUp,
@@ -4706,11 +4706,11 @@ var require_figures2 = __commonJS((exports, module) => {
4706
4706
  arrowRight: main.arrowRight,
4707
4707
  radioOn: "(*)",
4708
4708
  radioOff: "( )",
4709
- tick: "√",
4710
- cross: "×",
4709
+ tick: "\u221A",
4710
+ cross: "\xD7",
4711
4711
  ellipsis: "...",
4712
- pointerSmall: "»",
4713
- line: "─",
4712
+ pointerSmall: "\xBB",
4713
+ line: "\u2500",
4714
4714
  pointer: ">"
4715
4715
  };
4716
4716
  var figures = process.platform === "win32" ? win : main;
@@ -6969,9 +6969,9 @@ var ansiStyles = assembleStyles();
6969
6969
  var ansi_styles_default = ansiStyles;
6970
6970
 
6971
6971
  // node_modules/chalk/source/vendor/supports-color/index.js
6972
- import process2 from "node:process";
6973
- import os from "node:os";
6974
- import tty from "node:tty";
6972
+ import process2 from "process";
6973
+ import os from "os";
6974
+ import tty from "tty";
6975
6975
  function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
6976
6976
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
6977
6977
  const position = argv.indexOf(prefix + flag);
@@ -7282,31 +7282,31 @@ var source_default = chalk;
7282
7282
 
7283
7283
  // src/cli.ts
7284
7284
  var import_prompts = __toESM(require_prompts3(), 1);
7285
- import { homedir as homedir2 } from "node:os";
7286
- import { join as join2 } from "node:path";
7285
+ import { homedir as homedir2 } from "os";
7286
+ import { join as join3 } from "path";
7287
7287
 
7288
7288
  // src/config.ts
7289
- import { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
7290
- import { readFile, writeFile, copyFile, mkdir } from "node:fs/promises";
7291
- import { homedir } from "node:os";
7292
- import { join } from "node:path";
7289
+ import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, readlinkSync, rmSync } from "fs";
7290
+ import { readFile, writeFile, copyFile, mkdir, symlink } from "fs/promises";
7291
+ import { homedir } from "os";
7292
+ import { join } from "path";
7293
7293
 
7294
7294
  // src/templates.ts
7295
7295
  var PROVIDER_TEMPLATES = {
7296
7296
  glm: {
7297
7297
  name: "glm",
7298
- displayName: "GLM (智谱AI)",
7299
- description: "GLM-4.5 and GLM-4.6 models via z.ai",
7298
+ displayName: "GLM (\u667A\u8C31AI)",
7299
+ description: "GLM-4.5 and GLM-4.7 models via z.ai",
7300
7300
  settings: {
7301
7301
  env: {
7302
7302
  ANTHROPIC_AUTH_TOKEN: "",
7303
7303
  ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
7304
7304
  API_TIMEOUT_MS: "3000000",
7305
7305
  ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.5-air",
7306
- ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.6",
7307
- ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.6",
7308
- ANTHROPIC_MODEL: "glm-4.6",
7309
- ANTHROPIC_SMALL_FAST_MODEL: "glm-4.6-air",
7306
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.7",
7307
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.7",
7308
+ ANTHROPIC_MODEL: "glm-4.7",
7309
+ ANTHROPIC_SMALL_FAST_MODEL: "glm-4.5-air",
7310
7310
  ENABLE_THINKING: "true",
7311
7311
  REASONING_EFFORT: "high",
7312
7312
  MAX_THINKING_TOKENS: "8000",
@@ -7403,7 +7403,21 @@ async function listInstances() {
7403
7403
  const config = await loadConfig();
7404
7404
  return config.instances;
7405
7405
  }
7406
+ async function updateInstanceAutoSync(name, autoSync) {
7407
+ const config = await loadConfig();
7408
+ const index = config.instances.findIndex((i) => i.name === name);
7409
+ if (index === -1) {
7410
+ return null;
7411
+ }
7412
+ config.instances[index].autoSync = autoSync;
7413
+ await saveConfig(config);
7414
+ return config.instances[index];
7415
+ }
7416
+ var _testDefaultClaudeDir;
7406
7417
  function getDefaultClaudeDir() {
7418
+ if (_testDefaultClaudeDir) {
7419
+ return _testDefaultClaudeDir;
7420
+ }
7407
7421
  return join(homedir(), ".claude");
7408
7422
  }
7409
7423
  function hasDefaultClaudeConfig() {
@@ -7414,16 +7428,29 @@ function hasDefaultClaudeConfig() {
7414
7428
  async function copySettingsFromDefault(targetConfigDir) {
7415
7429
  const defaultDir = getDefaultClaudeDir();
7416
7430
  const sourceSettings = join(defaultDir, "settings.json");
7417
- const targetSettings = join(targetConfigDir, "settings.json");
7418
7431
  if (!existsSync(sourceSettings)) {
7419
7432
  throw new Error("Default Claude settings.json not found");
7420
7433
  }
7434
+ const content = await readFile(sourceSettings, "utf-8");
7435
+ const settings = JSON.parse(content);
7436
+ const safeSettings = {};
7437
+ const SAFE_SETTINGS = [
7438
+ "includeCoAuthoredBy",
7439
+ "alwaysThinkingEnabled",
7440
+ "enabledPlugins"
7441
+ ];
7442
+ for (const key of SAFE_SETTINGS) {
7443
+ if (settings[key] !== undefined) {
7444
+ safeSettings[key] = settings[key];
7445
+ }
7446
+ }
7421
7447
  if (!existsSync(targetConfigDir)) {
7422
7448
  await mkdir(targetConfigDir, { recursive: true });
7423
7449
  }
7424
- await copyFile(sourceSettings, targetSettings);
7450
+ const targetSettings = join(targetConfigDir, "settings.json");
7451
+ await writeFile(targetSettings, JSON.stringify(safeSettings, null, 2), "utf-8");
7425
7452
  }
7426
- async function copyAllFromDefault(targetConfigDir) {
7453
+ async function copyAllFromDefault(targetConfigDir, autoSync = true) {
7427
7454
  const defaultDir = getDefaultClaudeDir();
7428
7455
  if (!existsSync(defaultDir)) {
7429
7456
  throw new Error("Default Claude directory not found");
@@ -7431,6 +7458,7 @@ async function copyAllFromDefault(targetConfigDir) {
7431
7458
  if (!existsSync(targetConfigDir)) {
7432
7459
  await mkdir(targetConfigDir, { recursive: true });
7433
7460
  }
7461
+ const symlinkDirs = ["plugins", "skills"];
7434
7462
  const excludeFiles = [
7435
7463
  "config.json",
7436
7464
  ".config.json",
@@ -7455,10 +7483,24 @@ async function copyAllFromDefault(targetConfigDir) {
7455
7483
  continue;
7456
7484
  }
7457
7485
  if (stat.isDirectory()) {
7458
- if (!existsSync(targetPath)) {
7459
- await mkdir(targetPath, { recursive: true });
7486
+ if (autoSync && symlinkDirs.includes(entry)) {
7487
+ if (existsSync(targetPath)) {
7488
+ try {
7489
+ readlinkSync(targetPath);
7490
+ unlinkSync(targetPath);
7491
+ } catch {
7492
+ rmSync(targetPath, { force: true, recursive: true });
7493
+ }
7494
+ }
7495
+ const relativePath = join("..", "..", ".claude", entry);
7496
+ await symlink(relativePath, targetPath, "dir");
7497
+ console.log(` Symlinked: ${entry} -> ~/.claude/${entry}`);
7498
+ } else {
7499
+ if (!existsSync(targetPath)) {
7500
+ await mkdir(targetPath, { recursive: true });
7501
+ }
7502
+ await copyRecursive(sourcePath, targetPath);
7460
7503
  }
7461
- await copyRecursive(sourcePath, targetPath);
7462
7504
  } else {
7463
7505
  await copyFile(sourcePath, targetPath);
7464
7506
  }
@@ -7567,12 +7609,136 @@ async function createSettingsFromTemplate(targetConfigDir, template, apiKey) {
7567
7609
  const settingsFile = join(targetConfigDir, "settings.json");
7568
7610
  await writeFile(settingsFile, JSON.stringify(settings, null, 2), "utf-8");
7569
7611
  }
7612
+ async function syncPluginsAndSkills(configDir) {
7613
+ const defaultDir = getDefaultClaudeDir();
7614
+ const symlinkDirs = ["plugins", "skills"];
7615
+ if (!existsSync(configDir)) {
7616
+ throw new Error("Instance config directory does not exist");
7617
+ }
7618
+ for (const dir of symlinkDirs) {
7619
+ const targetPath = join(configDir, dir);
7620
+ const sourcePath = join(defaultDir, dir);
7621
+ if (!existsSync(sourcePath)) {
7622
+ console.log(source_default.yellow(` \u26A0 Source ${dir} not found in ~/.claude, skipping`));
7623
+ continue;
7624
+ }
7625
+ if (existsSync(targetPath)) {
7626
+ try {
7627
+ const linkTarget = readlinkSync(targetPath);
7628
+ const expectedTarget = join("..", "..", ".claude", dir);
7629
+ if (linkTarget === expectedTarget || linkTarget === sourcePath) {
7630
+ console.log(source_default.gray(` \u2713 ${dir} already synced`));
7631
+ continue;
7632
+ }
7633
+ rmSync(targetPath, { force: true });
7634
+ } catch {
7635
+ rmSync(targetPath, { force: true, recursive: true });
7636
+ }
7637
+ }
7638
+ const relativePath = join("..", "..", ".claude", dir);
7639
+ await symlink(relativePath, targetPath, "dir");
7640
+ console.log(source_default.green(` \u2713 Symlinked: ${dir} -> ~/.claude/${dir}`));
7641
+ }
7642
+ }
7643
+ async function unsyncPluginsAndSkills(configDir) {
7644
+ const defaultDir = getDefaultClaudeDir();
7645
+ const symlinkDirs = ["plugins", "skills"];
7646
+ if (!existsSync(configDir)) {
7647
+ throw new Error("Instance config directory does not exist");
7648
+ }
7649
+ for (const dir of symlinkDirs) {
7650
+ const targetPath = join(configDir, dir);
7651
+ const sourcePath = join(defaultDir, dir);
7652
+ if (!existsSync(sourcePath)) {
7653
+ console.log(source_default.yellow(` \u26A0 Source ${dir} not found in ~/.claude, skipping`));
7654
+ continue;
7655
+ }
7656
+ let isSymlink = false;
7657
+ try {
7658
+ readlinkSync(targetPath);
7659
+ isSymlink = true;
7660
+ } catch {}
7661
+ if (isSymlink) {
7662
+ rmSync(targetPath, { force: true });
7663
+ console.log(source_default.gray(` \u2713 Removed symlink for ${dir}`));
7664
+ } else if (!existsSync(targetPath)) {} else {
7665
+ console.log(source_default.yellow(` \u26A0 ${dir} is already a regular directory, skipping`));
7666
+ continue;
7667
+ }
7668
+ await mkdir(targetPath, { recursive: true });
7669
+ const copyRecursive = async (source, target) => {
7670
+ const entries = readdirSync(source);
7671
+ for (const entry of entries) {
7672
+ const sourceEntry = join(source, entry);
7673
+ const targetEntry = join(target, entry);
7674
+ const stat = statSync(sourceEntry);
7675
+ if (stat.isDirectory()) {
7676
+ await mkdir(targetEntry, { recursive: true });
7677
+ await copyRecursive(sourceEntry, targetEntry);
7678
+ } else {
7679
+ await copyFile(sourceEntry, targetEntry);
7680
+ }
7681
+ }
7682
+ };
7683
+ await copyRecursive(sourcePath, targetPath);
7684
+ console.log(source_default.green(` \u2713 Copied files for ${dir}`));
7685
+ }
7686
+ }
7687
+ async function readClaudeSettings(configDir) {
7688
+ const settingsFile = join(configDir, "settings.json");
7689
+ if (!existsSync(settingsFile)) {
7690
+ return null;
7691
+ }
7692
+ try {
7693
+ const content = await readFile(settingsFile, "utf-8");
7694
+ return JSON.parse(content);
7695
+ } catch (error) {
7696
+ console.error(source_default.yellow(`Warning: Failed to parse settings.json: ${error}`));
7697
+ return null;
7698
+ }
7699
+ }
7700
+ async function writeClaudeSettings(configDir, settings) {
7701
+ const settingsFile = join(configDir, "settings.json");
7702
+ if (!existsSync(configDir)) {
7703
+ await mkdir(configDir, { recursive: true });
7704
+ }
7705
+ await writeFile(settingsFile, JSON.stringify(settings, null, 2), "utf-8");
7706
+ }
7707
+ async function getEnabledPlugins(configDir) {
7708
+ const settings = await readClaudeSettings(configDir);
7709
+ return settings?.enabledPlugins || null;
7710
+ }
7711
+ async function setEnabledPlugins(configDir, enabledPlugins) {
7712
+ const settings = await readClaudeSettings(configDir) || {};
7713
+ settings.enabledPlugins = enabledPlugins;
7714
+ await writeClaudeSettings(configDir, settings);
7715
+ }
7716
+ async function enablePlugin(configDir, pluginId) {
7717
+ const settings = await readClaudeSettings(configDir) || {};
7718
+ if (!settings.enabledPlugins) {
7719
+ settings.enabledPlugins = {};
7720
+ }
7721
+ settings.enabledPlugins[pluginId] = true;
7722
+ await writeClaudeSettings(configDir, settings);
7723
+ }
7724
+ async function disablePlugin(configDir, pluginId) {
7725
+ const settings = await readClaudeSettings(configDir) || {};
7726
+ if (!settings.enabledPlugins) {
7727
+ settings.enabledPlugins = {};
7728
+ }
7729
+ settings.enabledPlugins[pluginId] = false;
7730
+ await writeClaudeSettings(configDir, settings);
7731
+ }
7732
+ async function listAvailablePlugins() {
7733
+ const defaultSettings = await readClaudeSettings(getDefaultClaudeDir());
7734
+ return defaultSettings?.enabledPlugins || null;
7735
+ }
7570
7736
 
7571
7737
  // src/wrapper.ts
7572
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync } from "node:fs";
7573
- import { writeFile as writeFile2 } from "node:fs/promises";
7574
- import { resolve, dirname } from "node:path";
7575
- import { execSync } from "node:child_process";
7738
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync as unlinkSync2 } from "fs";
7739
+ import { writeFile as writeFile2 } from "fs/promises";
7740
+ import { resolve, dirname } from "path";
7741
+ import { execSync } from "child_process";
7576
7742
  function getClaudePath() {
7577
7743
  try {
7578
7744
  const command = process.platform === "win32" ? "where claude" : "which claude";
@@ -7589,7 +7755,7 @@ function getClaudePath() {
7589
7755
  }
7590
7756
  function generateWrapperScript(options) {
7591
7757
  const claudePath = getClaudePath();
7592
- return `#!/usr/bin/env node
7758
+ return `#!/usr/bin/env bun
7593
7759
 
7594
7760
  // Claude Multi - Wrapper for ${options.name}
7595
7761
  // Generated by claude-multi
@@ -7639,20 +7805,48 @@ async function createWrapper(options) {
7639
7805
  }
7640
7806
  function removeWrapper(binaryPath) {
7641
7807
  if (existsSync2(binaryPath)) {
7642
- unlinkSync(binaryPath);
7808
+ unlinkSync2(binaryPath);
7643
7809
  }
7644
7810
  }
7645
7811
  function getDefaultBinaryPath(name) {
7646
- const binDir = process.platform === "win32" ? resolve(process.env.APPDATA || "", "npm") : resolve(process.env.HOME || "", ".local", "bin");
7812
+ const binDir = process.platform === "win32" ? resolve(process.env.LOCALAPPDATA || "", "bun", "bin") : resolve(process.env.HOME || "", ".local", "bin");
7647
7813
  const binaryName = process.platform === "win32" ? `claude-${name}.cmd` : `claude-${name}`;
7648
7814
  return resolve(binDir, binaryName);
7649
7815
  }
7650
7816
 
7651
7817
  // src/version.ts
7652
- import { execSync as execSync2 } from "node:child_process";
7818
+ import { execSync as execSync2 } from "child_process";
7819
+ import { readFileSync } from "fs";
7820
+ import { join as join2 } from "path";
7821
+ function getClaudeMultiVersion() {
7822
+ const pkgPath = join2(process.cwd(), "package.json");
7823
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
7824
+ return pkg.version;
7825
+ }
7826
+ async function getLatestClaudeMultiVersion() {
7827
+ const response = await fetch("https://registry.npmjs.org/claude-multi/latest");
7828
+ const data = await response.json();
7829
+ return data.version;
7830
+ }
7831
+ async function checkForClaudeMultiUpdates() {
7832
+ try {
7833
+ const current = getClaudeMultiVersion();
7834
+ const latest = await getLatestClaudeMultiVersion();
7835
+ return {
7836
+ current,
7837
+ latest,
7838
+ updateAvailable: current !== latest
7839
+ };
7840
+ } catch {
7841
+ return { current: "", latest: "", updateAvailable: false };
7842
+ }
7843
+ }
7844
+ function upgradeClaudeMulti() {
7845
+ execSync2("bun upgrade -g claude-multi", { stdio: "inherit" });
7846
+ }
7653
7847
  function getCurrentVersion() {
7654
7848
  try {
7655
- const output = execSync2("npm list -g @anthropic-ai/claude-code --json", {
7849
+ const output = execSync2("bun pm ls -g @anthropic-ai/claude-code --json", {
7656
7850
  encoding: "utf-8",
7657
7851
  stdio: ["pipe", "pipe", "ignore"]
7658
7852
  });
@@ -7665,7 +7859,7 @@ function getCurrentVersion() {
7665
7859
  }
7666
7860
  async function getLatestVersion() {
7667
7861
  try {
7668
- const output = execSync2("npm view @anthropic-ai/claude-code version", {
7862
+ const output = execSync2("bun pm npm view @anthropic-ai/claude-code version", {
7669
7863
  encoding: "utf-8",
7670
7864
  stdio: ["pipe", "pipe", "ignore"]
7671
7865
  });
@@ -7686,7 +7880,7 @@ async function checkForUpdates() {
7686
7880
  async function updateClaudeCode() {
7687
7881
  try {
7688
7882
  console.log("Updating @anthropic-ai/claude-code...");
7689
- execSync2("npm install -g @anthropic-ai/claude-code@latest", {
7883
+ execSync2("bun install -g @anthropic-ai/claude-code@latest", {
7690
7884
  stdio: "inherit"
7691
7885
  });
7692
7886
  console.log("Update completed successfully!");
@@ -7697,10 +7891,10 @@ async function updateClaudeCode() {
7697
7891
 
7698
7892
  // src/cli.ts
7699
7893
  var program2 = new Command;
7700
- program2.name("claude-multi").description("Manage multiple Claude Code instances with different aliases").version("0.3.0");
7701
- program2.command("add <name>").description("Add a new Claude Code instance").option("-c, --config <dir>", "Config directory path", (name) => join2(homedir2(), `.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)").option("--api-key <key>", "API key for the provider").action(async (name, options) => {
7894
+ program2.name("claude-multi").description("Manage multiple Claude Code instances with different aliases").version("0.4.0");
7895
+ program2.command("add <name>").description("Add a new Claude Code instance").option("-c, --config <dir>", "Config directory path", (name) => join3(homedir2(), `.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)").option("--api-key <key>", "API key for the provider").option("--auto-sync", "Auto-sync plugins/skills via symlinks (default)").option("--manual", "Manually manage plugins/skills (copy files)").action(async (name, options) => {
7702
7896
  try {
7703
- const configDir = options.config || join2(homedir2(), `.claude-${name}`);
7897
+ const configDir = options.config || join3(homedir2(), `.claude-${name}`);
7704
7898
  const binaryPath = options.binary || getDefaultBinaryPath(name);
7705
7899
  const hasDefaultConfig = hasDefaultClaudeConfig();
7706
7900
  const hasDefaultMcp = await hasDefaultMcpConfig();
@@ -7710,14 +7904,15 @@ program2.command("add <name>").description("Add a new Claude Code instance").opt
7710
7904
  let useProviderTemplate = false;
7711
7905
  let providerTemplate = null;
7712
7906
  let apiKey = "";
7907
+ let autoSync = !options.manual;
7713
7908
  if (options.provider) {
7714
7909
  providerTemplate = getProviderTemplate(options.provider);
7715
7910
  if (!providerTemplate) {
7716
- console.error(source_default.red(`✗ Unknown provider '${options.provider}'. Available: glm, minimax`));
7911
+ console.error(source_default.red(`\u2717 Unknown provider '${options.provider}'. Available: glm, minimax`));
7717
7912
  process.exit(1);
7718
7913
  }
7719
7914
  if (!options.apiKey) {
7720
- console.error(source_default.red("✗ --api-key is required when using --provider"));
7915
+ console.error(source_default.red("\u2717 --api-key is required when using --provider"));
7721
7916
  process.exit(1);
7722
7917
  }
7723
7918
  apiKey = options.apiKey;
@@ -7765,7 +7960,7 @@ Found existing Claude Code configuration at ~/.claude`));
7765
7960
  ]);
7766
7961
  if (response.copyOption === undefined) {
7767
7962
  console.log(source_default.yellow(`
7768
- ✗ Cancelled`));
7963
+ \u2717 Cancelled`));
7769
7964
  process.exit(0);
7770
7965
  }
7771
7966
  copySettings = response.copyOption === "settings" || response.copyOption === "settings+mcp" || response.copyOption === "all";
@@ -7776,34 +7971,39 @@ Found existing Claude Code configuration at ~/.claude`));
7776
7971
  name,
7777
7972
  configDir,
7778
7973
  binaryPath,
7779
- createdAt: new Date().toISOString()
7974
+ createdAt: new Date().toISOString(),
7975
+ autoSync
7780
7976
  };
7781
7977
  await addInstance(instance);
7782
7978
  await createWrapper(instance);
7783
7979
  if (copySettings && !copyAllFiles) {
7784
7980
  await copySettingsFromDefault(configDir);
7785
- console.log(source_default.green("✓ Copied settings.json"));
7981
+ console.log(source_default.green("\u2713 Copied settings.json"));
7786
7982
  }
7787
7983
  if (copyMcpServers && !copyAllFiles) {
7788
7984
  try {
7789
7985
  await copyMcpServersFromDefault(configDir);
7790
- console.log(source_default.green("✓ Copied MCP server configurations"));
7986
+ console.log(source_default.green("\u2713 Copied MCP server configurations"));
7791
7987
  } catch (error) {
7792
- console.log(source_default.yellow(`⚠ Warning: ${error.message}`));
7988
+ console.log(source_default.yellow(`\u26A0 Warning: ${error.message}`));
7793
7989
  }
7794
7990
  }
7795
7991
  if (copyAllFiles) {
7796
- await copyAllFromDefault(configDir);
7797
- console.log(source_default.green("✓ Copied all files from default Claude"));
7992
+ await copyAllFromDefault(configDir, autoSync);
7993
+ if (autoSync) {
7994
+ console.log(source_default.green("\u2713 Copied all files with auto-sync (plugins/skills symlinked)"));
7995
+ } else {
7996
+ console.log(source_default.green("\u2713 Copied all files from default Claude (manual mode)"));
7997
+ }
7798
7998
  }
7799
7999
  if (useProviderTemplate && providerTemplate && !copySettings && !copyAllFiles) {
7800
8000
  await createSettingsFromTemplate(configDir, providerTemplate, apiKey);
7801
- console.log(source_default.green(`✓ Applied ${providerTemplate.displayName} template`));
8001
+ console.log(source_default.green(`\u2713 Applied ${providerTemplate.displayName} template`));
7802
8002
  } else if (useProviderTemplate && (copySettings || copyAllFiles)) {
7803
- console.log(source_default.yellow("⚠ Provider template skipped (copied settings from default Claude)"));
8003
+ console.log(source_default.yellow("\u26A0 Provider template skipped (copied settings from default Claude)"));
7804
8004
  }
7805
8005
  console.log(source_default.green(`
7806
- ✓ Instance '${name}' created successfully!`));
8006
+ \u2713 Instance '${name}' created successfully!`));
7807
8007
  console.log(source_default.gray(` Binary: ${binaryPath}`));
7808
8008
  console.log(source_default.gray(` Config: ${configDir}`));
7809
8009
  console.log();
@@ -7811,7 +8011,7 @@ Found existing Claude Code configuration at ~/.claude`));
7811
8011
  const pathEnv = process.env.PATH || "";
7812
8012
  const isInPath = pathEnv.split(":").some((p) => p === binDir);
7813
8013
  if (!isInPath) {
7814
- console.log(source_default.yellow(`⚠ Warning: ${binDir} is not in your PATH`));
8014
+ console.log(source_default.yellow(`\u26A0 Warning: ${binDir} is not in your PATH`));
7815
8015
  console.log(source_default.gray(`Add to PATH by running:`));
7816
8016
  console.log(source_default.cyan(` echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc`));
7817
8017
  console.log(source_default.cyan(` source ~/.zshrc`));
@@ -7821,7 +8021,7 @@ Found existing Claude Code configuration at ~/.claude`));
7821
8021
  console.log(source_default.cyan(`Run: claude-${name} --help`));
7822
8022
  }
7823
8023
  } catch (error) {
7824
- console.error(source_default.red(`✗ Error: ${error.message}`));
8024
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
7825
8025
  process.exit(1);
7826
8026
  }
7827
8027
  });
@@ -7829,7 +8029,7 @@ program2.command("remove <name>").alias("rm").description("Remove a Claude Code
7829
8029
  try {
7830
8030
  const instance = await getInstance(name);
7831
8031
  if (!instance) {
7832
- console.error(source_default.red(`✗ Instance '${name}' not found`));
8032
+ console.error(source_default.red(`\u2717 Instance '${name}' not found`));
7833
8033
  process.exit(1);
7834
8034
  }
7835
8035
  if (!options.force) {
@@ -7843,11 +8043,11 @@ program2.command("remove <name>").alias("rm").description("Remove a Claude Code
7843
8043
  }
7844
8044
  await removeInstance(name);
7845
8045
  removeWrapper(instance.binaryPath);
7846
- console.log(source_default.green(`✓ Instance '${name}' removed successfully!`));
8046
+ console.log(source_default.green(`\u2713 Instance '${name}' removed successfully!`));
7847
8047
  console.log();
7848
8048
  console.log(source_default.gray(`To remove config files, run: rm -rf ${instance.configDir}`));
7849
8049
  } catch (error) {
7850
- console.error(source_default.red(`✗ Error: ${error.message}`));
8050
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
7851
8051
  process.exit(1);
7852
8052
  }
7853
8053
  });
@@ -7863,14 +8063,16 @@ program2.command("list").alias("ls").description("List all Claude Code instances
7863
8063
  console.log(source_default.bold(`Found ${instances.length} instance(s):
7864
8064
  `));
7865
8065
  for (const instance of instances) {
7866
- console.log(source_default.cyan(`● ${instance.name}`));
8066
+ console.log(source_default.cyan(`\u25CF ${instance.name}`));
7867
8067
  console.log(source_default.gray(` Binary: ${instance.binaryPath}`));
7868
8068
  console.log(source_default.gray(` Config: ${instance.configDir}`));
7869
8069
  console.log(source_default.gray(` Created: ${new Date(instance.createdAt).toLocaleString()}`));
8070
+ const autoSyncStatus = instance.autoSync !== false ? source_default.green("on") : source_default.yellow("off");
8071
+ console.log(source_default.gray(` Auto-sync: ${autoSyncStatus}`));
7870
8072
  console.log();
7871
8073
  }
7872
8074
  } catch (error) {
7873
- console.error(source_default.red(`✗ Error: ${error.message}`));
8075
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
7874
8076
  process.exit(1);
7875
8077
  }
7876
8078
  });
@@ -7878,7 +8080,7 @@ program2.command("info <name>").description("Show details about a specific insta
7878
8080
  try {
7879
8081
  const instance = await getInstance(name);
7880
8082
  if (!instance) {
7881
- console.error(source_default.red(`✗ Instance '${name}' not found`));
8083
+ console.error(source_default.red(`\u2717 Instance '${name}' not found`));
7882
8084
  process.exit(1);
7883
8085
  }
7884
8086
  console.log(source_default.bold(`Instance: ${source_default.cyan(instance.name)}
@@ -7886,8 +8088,10 @@ program2.command("info <name>").description("Show details about a specific insta
7886
8088
  console.log(`${source_default.gray("Binary:")} ${instance.binaryPath}`);
7887
8089
  console.log(`${source_default.gray("Config:")} ${instance.configDir}`);
7888
8090
  console.log(`${source_default.gray("Created:")} ${new Date(instance.createdAt).toLocaleString()}`);
8091
+ const autoSyncStatus = instance.autoSync !== false ? source_default.green("\u2713 Enabled") : source_default.yellow("\u2717 Disabled");
8092
+ console.log(`${source_default.gray("Auto-sync:")} ${autoSyncStatus}`);
7889
8093
  } catch (error) {
7890
- console.error(source_default.red(`✗ Error: ${error.message}`));
8094
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
7891
8095
  process.exit(1);
7892
8096
  }
7893
8097
  });
@@ -7904,13 +8108,13 @@ program2.command("version").description("Check Claude Code version and updates")
7904
8108
  console.log(`${source_default.gray("Latest:")} ${source_default.cyan(versionInfo.latest)}`);
7905
8109
  console.log();
7906
8110
  if (versionInfo.updateAvailable) {
7907
- console.log(source_default.yellow(`⚠ Update available: ${versionInfo.latest}`));
8111
+ console.log(source_default.yellow(`\u26A0 Update available: ${versionInfo.latest}`));
7908
8112
  console.log(source_default.gray("Run 'claude-multi update' to update"));
7909
8113
  } else if (versionInfo.current) {
7910
- console.log(source_default.green("✓ You're up to date!"));
8114
+ console.log(source_default.green("\u2713 You're up to date!"));
7911
8115
  }
7912
8116
  } catch (error) {
7913
- console.error(source_default.red(`✗ Error: ${error.message}`));
8117
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
7914
8118
  process.exit(1);
7915
8119
  }
7916
8120
  });
@@ -7918,20 +8122,206 @@ program2.command("update").description("Update @anthropic-ai/claude-code to the
7918
8122
  try {
7919
8123
  const versionInfo = await checkForUpdates();
7920
8124
  if (!versionInfo.updateAvailable && versionInfo.current) {
7921
- console.log(source_default.green(`✓ Already up to date (${versionInfo.current})`));
8125
+ console.log(source_default.green(`\u2713 Already up to date (${versionInfo.current})`));
7922
8126
  return;
7923
8127
  }
7924
8128
  await updateClaudeCode();
7925
8129
  } catch (error) {
7926
- console.error(source_default.red(`✗ Error: ${error.message}`));
8130
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
8131
+ process.exit(1);
8132
+ }
8133
+ });
8134
+ program2.command("auto-sync <name> <status>").description("Toggle auto-sync for plugins/skills (on/off)").action(async (name, status) => {
8135
+ try {
8136
+ const instance = await getInstance(name);
8137
+ if (!instance) {
8138
+ console.error(source_default.red(`\u2717 Instance '${name}' not found`));
8139
+ process.exit(1);
8140
+ }
8141
+ const newStatus = status.toLowerCase() === "on" || status.toLowerCase() === "true" || status === "1";
8142
+ const currentStatus = instance.autoSync !== false;
8143
+ if (currentStatus === newStatus) {
8144
+ console.log(source_default.yellow(`Auto-sync is already ${newStatus ? "enabled" : "disabled"} for '${name}'`));
8145
+ return;
8146
+ }
8147
+ await updateInstanceAutoSync(name, newStatus);
8148
+ console.log(source_default.bold(`
8149
+ \uD83D\uDD04 ${newStatus ? "Enabling" : "Disabling"} auto-sync for '${name}'...
8150
+ `));
8151
+ if (newStatus) {
8152
+ await syncPluginsAndSkills(instance.configDir);
8153
+ } else {
8154
+ await unsyncPluginsAndSkills(instance.configDir);
8155
+ }
8156
+ console.log(source_default.green(`
8157
+ \u2713 Auto-sync ${newStatus ? "enabled" : "disabled"} for '${name}'`));
8158
+ } catch (error) {
8159
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
8160
+ process.exit(1);
8161
+ }
8162
+ });
8163
+ program2.command("plugins").description("Manage enabled plugins for instances").argument("[action]", "Action to perform (list, enable, disable, copy)", "list").argument("[instance]", "Instance name (for list/enable/disable)", "").argument("[plugins...]", "Plugin IDs to enable/disable", []).action(async (action = "list", instanceName = "", plugins = []) => {
8164
+ try {
8165
+ switch (action) {
8166
+ case "list":
8167
+ await handlePluginsList(instanceName);
8168
+ break;
8169
+ case "enable":
8170
+ await handlePluginsEnable(instanceName, plugins);
8171
+ break;
8172
+ case "disable":
8173
+ await handlePluginsDisable(instanceName, plugins);
8174
+ break;
8175
+ case "copy":
8176
+ await handlePluginsCopy(instanceName);
8177
+ break;
8178
+ default:
8179
+ console.error(source_default.red(`\u2717 Unknown action: ${action}`));
8180
+ console.log(source_default.gray("Available actions: list, enable, disable, copy"));
8181
+ process.exit(1);
8182
+ }
8183
+ } catch (error) {
8184
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
7927
8185
  process.exit(1);
7928
8186
  }
7929
8187
  });
8188
+ async function handlePluginsList(instanceName) {
8189
+ const instances = await listInstances();
8190
+ if (!instanceName) {
8191
+ console.log(source_default.bold(`
8192
+ \uD83D\uDCCB Enabled Plugins by Instance
8193
+ `));
8194
+ const defaultPlugins = await listAvailablePlugins();
8195
+ if (defaultPlugins) {
8196
+ console.log(source_default.gray(`Default Claude (~/.claude):`));
8197
+ const enabledCount = Object.values(defaultPlugins).filter((v) => v === true).length;
8198
+ console.log(source_default.gray(` Plugins: ${enabledCount} enabled
8199
+ `));
8200
+ for (const [pluginId, enabled] of Object.entries(defaultPlugins)) {
8201
+ const status = enabled ? source_default.green("\u2713") : source_default.gray("\u2717");
8202
+ console.log(source_default.gray(` ${status} ${pluginId}`));
8203
+ }
8204
+ console.log();
8205
+ }
8206
+ if (instances.length === 0) {
8207
+ console.log(source_default.yellow("No instances found."));
8208
+ return;
8209
+ }
8210
+ for (const instance of instances) {
8211
+ const plugins = await getEnabledPlugins(instance.configDir);
8212
+ console.log(source_default.cyan(`${instance.name}:`));
8213
+ if (plugins) {
8214
+ const enabledCount = Object.values(plugins).filter((v) => v === true).length;
8215
+ console.log(source_default.gray(` Plugins: ${enabledCount} enabled
8216
+ `));
8217
+ for (const [pluginId, enabled] of Object.entries(plugins)) {
8218
+ const status = enabled ? source_default.green("\u2713") : source_default.gray("\u2717");
8219
+ console.log(source_default.gray(` ${status} ${pluginId}`));
8220
+ }
8221
+ } else {
8222
+ console.log(source_default.yellow(" No plugins configured"));
8223
+ }
8224
+ console.log();
8225
+ }
8226
+ } else {
8227
+ const instance = await getInstance(instanceName);
8228
+ if (!instance) {
8229
+ console.error(source_default.red(`\u2717 Instance '${instanceName}' not found`));
8230
+ process.exit(1);
8231
+ }
8232
+ const plugins = await getEnabledPlugins(instance.configDir);
8233
+ if (!plugins) {
8234
+ console.log(source_default.yellow(`No plugins configured for '${instanceName}'`));
8235
+ return;
8236
+ }
8237
+ console.log(source_default.bold(`
8238
+ \uD83D\uDCCB Enabled Plugins for '${instanceName}'
8239
+ `));
8240
+ for (const [pluginId, enabled] of Object.entries(plugins)) {
8241
+ const status = enabled ? source_default.green("\u2713") : source_default.gray("\u2717");
8242
+ console.log(`${status} ${source_default.cyan(pluginId)}`);
8243
+ }
8244
+ }
8245
+ }
8246
+ async function handlePluginsEnable(instanceName, plugins) {
8247
+ const instance = await getInstance(instanceName);
8248
+ if (!instance) {
8249
+ console.error(source_default.red(`\u2717 Instance '${instanceName}' not found`));
8250
+ process.exit(1);
8251
+ }
8252
+ if (plugins.length === 0) {
8253
+ console.error(source_default.red("\u2717 No plugins specified"));
8254
+ console.log(source_default.gray("Usage: claude-multi plugins enable <instance> <plugin-id>..."));
8255
+ process.exit(1);
8256
+ }
8257
+ const currentPlugins = await getEnabledPlugins(instance.configDir) || {};
8258
+ let updated = false;
8259
+ for (const pluginId of plugins) {
8260
+ if (currentPlugins[pluginId] === true) {
8261
+ console.log(source_default.yellow(`\u26A0 Plugin '${pluginId}' is already enabled`));
8262
+ } else {
8263
+ currentPlugins[pluginId] = true;
8264
+ await enablePlugin(instance.configDir, pluginId);
8265
+ console.log(source_default.green(`\u2713 Enabled plugin '${pluginId}'`));
8266
+ updated = true;
8267
+ }
8268
+ }
8269
+ if (updated) {
8270
+ console.log(source_default.green(`
8271
+ \u2713 Updated plugins for '${instanceName}'`));
8272
+ }
8273
+ }
8274
+ async function handlePluginsDisable(instanceName, plugins) {
8275
+ const instance = await getInstance(instanceName);
8276
+ if (!instance) {
8277
+ console.error(source_default.red(`\u2717 Instance '${instanceName}' not found`));
8278
+ process.exit(1);
8279
+ }
8280
+ if (plugins.length === 0) {
8281
+ console.error(source_default.red("\u2717 No plugins specified"));
8282
+ console.log(source_default.gray("Usage: claude-multi plugins disable <instance> <plugin-id>..."));
8283
+ process.exit(1);
8284
+ }
8285
+ const currentPlugins = await getEnabledPlugins(instance.configDir) || {};
8286
+ let updated = false;
8287
+ for (const pluginId of plugins) {
8288
+ if (currentPlugins[pluginId] === false) {
8289
+ console.log(source_default.yellow(`\u26A0 Plugin '${pluginId}' is already disabled`));
8290
+ } else {
8291
+ currentPlugins[pluginId] = false;
8292
+ await disablePlugin(instance.configDir, pluginId);
8293
+ console.log(source_default.green(`\u2713 Disabled plugin '${pluginId}'`));
8294
+ updated = true;
8295
+ }
8296
+ }
8297
+ if (updated) {
8298
+ console.log(source_default.green(`
8299
+ \u2713 Updated plugins for '${instanceName}'`));
8300
+ }
8301
+ }
8302
+ async function handlePluginsCopy(instanceName) {
8303
+ const instance = await getInstance(instanceName);
8304
+ if (!instance) {
8305
+ console.error(source_default.red(`\u2717 Instance '${instanceName}' not found`));
8306
+ process.exit(1);
8307
+ }
8308
+ const defaultPlugins = await listAvailablePlugins();
8309
+ if (!defaultPlugins) {
8310
+ console.log(source_default.yellow("No plugins found in default Claude settings"));
8311
+ return;
8312
+ }
8313
+ console.log(source_default.bold(`
8314
+ \uD83D\uDCCB Copying plugins from default Claude to '${instanceName}'
8315
+ `));
8316
+ await setEnabledPlugins(instance.configDir, defaultPlugins);
8317
+ const enabledCount = Object.values(defaultPlugins).filter((v) => v === true).length;
8318
+ console.log(source_default.green(`\u2713 Copied ${enabledCount} enabled plugins to '${instanceName}'`));
8319
+ }
7930
8320
  program2.command("interactive").alias("i").description("Launch interactive mode for managing Claude Code instances").action(async () => {
7931
8321
  try {
7932
8322
  await runInteractiveMode();
7933
8323
  } catch (error) {
7934
- console.error(source_default.red(`✗ Error: ${error.message}`));
8324
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
7935
8325
  process.exit(1);
7936
8326
  }
7937
8327
  });
@@ -7952,11 +8342,13 @@ async function runInteractiveMode() {
7952
8342
  name: "action",
7953
8343
  message: "What would you like to do?",
7954
8344
  choices: [
7955
- { title: "➕ Add new instance", value: "add" },
8345
+ { title: "\u2795 Add new instance", value: "add" },
7956
8346
  { title: "\uD83D\uDCCB List all instances", value: "list" },
7957
8347
  ...instances.length > 0 ? [
7958
- { title: "ℹ️ Show instance details", value: "info" },
7959
- { title: "\uD83D\uDDD1️ Remove instance", value: "remove" }
8348
+ { title: "\u2139\uFE0F Show instance details", value: "info" },
8349
+ { title: "\uD83D\uDD0C Manage plugins", value: "plugins" },
8350
+ { title: "\uD83D\uDD04 Toggle auto-sync", value: "autosync" },
8351
+ { title: "\uD83D\uDDD1\uFE0F Remove instance", value: "remove" }
7960
8352
  ] : [],
7961
8353
  { title: "\uD83D\uDEAA Exit", value: "exit" }
7962
8354
  ],
@@ -7977,6 +8369,12 @@ async function runInteractiveMode() {
7977
8369
  case "info":
7978
8370
  await handleShowInstanceInfo(instances);
7979
8371
  break;
8372
+ case "plugins":
8373
+ await handleManagePlugins(instances);
8374
+ break;
8375
+ case "autosync":
8376
+ await handleToggleAutoSync(instances);
8377
+ break;
7980
8378
  case "remove":
7981
8379
  await handleRemoveInstance(instances);
7982
8380
  break;
@@ -8008,7 +8406,7 @@ async function runInteractiveMode() {
8008
8406
  }
8009
8407
  async function handleAddInstance() {
8010
8408
  console.log(source_default.bold(`
8011
- ➕ Add New Instance
8409
+ \u2795 Add New Instance
8012
8410
  `));
8013
8411
  const { name } = await import_prompts.default({
8014
8412
  type: "text",
@@ -8027,7 +8425,7 @@ async function handleAddInstance() {
8027
8425
  return;
8028
8426
  const existing = await getInstance(name);
8029
8427
  if (existing) {
8030
- console.log(source_default.red(`✗ Instance '${name}' already exists`));
8428
+ console.log(source_default.red(`\u2717 Instance '${name}' already exists`));
8031
8429
  return;
8032
8430
  }
8033
8431
  const { useProvider } = await import_prompts.default({
@@ -8082,7 +8480,7 @@ async function handleAddInstance() {
8082
8480
  let configDir;
8083
8481
  let binaryPath;
8084
8482
  if (useDefaults) {
8085
- configDir = join2(homedir2(), `.claude-${name}`);
8483
+ configDir = join3(homedir2(), `.claude-${name}`);
8086
8484
  binaryPath = getDefaultBinaryPath(name);
8087
8485
  } else {
8088
8486
  const configResponse = await import_prompts.default([
@@ -8090,7 +8488,7 @@ async function handleAddInstance() {
8090
8488
  type: "text",
8091
8489
  name: "configDir",
8092
8490
  message: "Config directory path:",
8093
- initial: join2(homedir2(), `.claude-${name}`)
8491
+ initial: join3(homedir2(), `.claude-${name}`)
8094
8492
  },
8095
8493
  {
8096
8494
  type: "text",
@@ -8139,45 +8537,63 @@ async function handleAddInstance() {
8139
8537
  copyMcpServers = copyOption === "mcp" || copyOption === "settings+mcp" || copyOption === "all";
8140
8538
  copyAllFiles = copyOption === "all";
8141
8539
  }
8540
+ let autoSync = true;
8541
+ if (copyAllFiles) {
8542
+ const { autoSync: autoSyncResponse } = await import_prompts.default({
8543
+ type: "confirm",
8544
+ name: "autoSync",
8545
+ message: "Auto-sync plugins and skills via symlinks?",
8546
+ initial: true,
8547
+ hint: "When enabled, plugins/skills are symlinked from ~/.claude (shared across instances)"
8548
+ });
8549
+ if (autoSyncResponse === undefined)
8550
+ return;
8551
+ autoSync = autoSyncResponse;
8552
+ }
8142
8553
  const instance = {
8143
8554
  name,
8144
8555
  configDir,
8145
8556
  binaryPath,
8146
- createdAt: new Date().toISOString()
8557
+ createdAt: new Date().toISOString(),
8558
+ autoSync
8147
8559
  };
8148
8560
  await addInstance(instance);
8149
8561
  await createWrapper(instance);
8150
8562
  if (copySettings && !copyAllFiles) {
8151
8563
  await copySettingsFromDefault(configDir);
8152
- console.log(source_default.green("✓ Copied settings.json"));
8564
+ console.log(source_default.green("\u2713 Copied settings.json"));
8153
8565
  }
8154
8566
  if (copyMcpServers && !copyAllFiles) {
8155
8567
  try {
8156
8568
  await copyMcpServersFromDefault(configDir);
8157
- console.log(source_default.green("✓ Copied MCP server configurations"));
8569
+ console.log(source_default.green("\u2713 Copied MCP server configurations"));
8158
8570
  } catch (error) {
8159
- console.log(source_default.yellow(`⚠ Warning: ${error.message}`));
8571
+ console.log(source_default.yellow(`\u26A0 Warning: ${error.message}`));
8160
8572
  }
8161
8573
  }
8162
8574
  if (copyAllFiles) {
8163
- await copyAllFromDefault(configDir);
8164
- console.log(source_default.green("✓ Copied all files from default Claude"));
8575
+ await copyAllFromDefault(configDir, autoSync);
8576
+ if (autoSync) {
8577
+ console.log(source_default.green("\u2713 Copied all files with auto-sync (plugins/skills symlinked)"));
8578
+ } else {
8579
+ console.log(source_default.green("\u2713 Copied all files from default Claude (manual mode)"));
8580
+ }
8165
8581
  }
8166
8582
  if (providerTemplate && !copySettings && !copyAllFiles) {
8167
8583
  await createSettingsFromTemplate(configDir, providerTemplate, apiKey);
8168
- console.log(source_default.green(`✓ Applied ${providerTemplate.displayName} template`));
8584
+ console.log(source_default.green(`\u2713 Applied ${providerTemplate.displayName} template`));
8169
8585
  } else if (providerTemplate && (copySettings || copyAllFiles)) {
8170
- console.log(source_default.yellow("⚠ Provider template skipped (copied settings from default Claude)"));
8586
+ console.log(source_default.yellow("\u26A0 Provider template skipped (copied settings from default Claude)"));
8171
8587
  }
8172
8588
  console.log(source_default.green(`
8173
- ✓ Instance '${name}' created successfully!`));
8589
+ \u2713 Instance '${name}' created successfully!`));
8174
8590
  console.log(source_default.gray(` Binary: ${binaryPath}`));
8175
8591
  console.log(source_default.gray(` Config: ${configDir}`));
8176
8592
  const binDir = binaryPath.substring(0, binaryPath.lastIndexOf("/"));
8177
8593
  const pathEnv = process.env.PATH || "";
8178
8594
  const isInPath = pathEnv.split(":").some((p) => p === binDir);
8179
8595
  if (!isInPath) {
8180
- console.log(source_default.yellow(`⚠ Warning: ${binDir} is not in your PATH`));
8596
+ console.log(source_default.yellow(`\u26A0 Warning: ${binDir} is not in your PATH`));
8181
8597
  console.log(source_default.gray(`Add to PATH by running:`));
8182
8598
  console.log(source_default.cyan(` echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc`));
8183
8599
  console.log(source_default.cyan(` source ~/.zshrc`));
@@ -8195,10 +8611,12 @@ async function handleListInstances(instances) {
8195
8611
  return;
8196
8612
  }
8197
8613
  for (const instance of instances) {
8198
- console.log(source_default.cyan(`● ${instance.name}`));
8614
+ console.log(source_default.cyan(`\u25CF ${instance.name}`));
8199
8615
  console.log(source_default.gray(` Binary: ${instance.binaryPath}`));
8200
8616
  console.log(source_default.gray(` Config: ${instance.configDir}`));
8201
8617
  console.log(source_default.gray(` Created: ${new Date(instance.createdAt).toLocaleString()}`));
8618
+ const autoSyncStatus = instance.autoSync !== false ? source_default.green("on") : source_default.yellow("off");
8619
+ console.log(source_default.gray(` Auto-sync: ${autoSyncStatus}`));
8202
8620
  console.log();
8203
8621
  }
8204
8622
  }
@@ -8208,7 +8626,7 @@ async function handleShowInstanceInfo(instances) {
8208
8626
  return;
8209
8627
  }
8210
8628
  console.log(source_default.bold(`
8211
- ℹ️ Instance Details
8629
+ \u2139\uFE0F Instance Details
8212
8630
  `));
8213
8631
  const { instanceName } = await import_prompts.default({
8214
8632
  type: "select",
@@ -8229,6 +8647,8 @@ Instance: ${source_default.cyan(instance.name)}`));
8229
8647
  console.log(`${source_default.gray("Binary:")} ${instance.binaryPath}`);
8230
8648
  console.log(`${source_default.gray("Config:")} ${instance.configDir}`);
8231
8649
  console.log(`${source_default.gray("Created:")} ${new Date(instance.createdAt).toLocaleString()}`);
8650
+ const autoSyncStatus = instance.autoSync !== false ? source_default.green("\u2713 Enabled") : source_default.yellow("\u2717 Disabled");
8651
+ console.log(`${source_default.gray("Auto-sync:")} ${autoSyncStatus}`);
8232
8652
  }
8233
8653
  async function handleRemoveInstance(instances) {
8234
8654
  if (instances.length === 0) {
@@ -8236,7 +8656,7 @@ async function handleRemoveInstance(instances) {
8236
8656
  return;
8237
8657
  }
8238
8658
  console.log(source_default.bold(`
8239
- \uD83D\uDDD1️ Remove Instance
8659
+ \uD83D\uDDD1\uFE0F Remove Instance
8240
8660
  `));
8241
8661
  const { instanceName } = await import_prompts.default({
8242
8662
  type: "select",
@@ -8259,14 +8679,176 @@ async function handleRemoveInstance(instances) {
8259
8679
  initial: false
8260
8680
  });
8261
8681
  if (!confirm) {
8262
- console.log(source_default.yellow("✗ Cancelled"));
8682
+ console.log(source_default.yellow("\u2717 Cancelled"));
8263
8683
  return;
8264
8684
  }
8265
8685
  await removeInstance(instanceName);
8266
8686
  removeWrapper(instance.binaryPath);
8267
- console.log(source_default.green(`✓ Instance '${instanceName}' removed successfully!`));
8687
+ console.log(source_default.green(`\u2713 Instance '${instanceName}' removed successfully!`));
8268
8688
  console.log(source_default.gray(`To remove config files, run: rm -rf ${instance.configDir}`));
8269
8689
  }
8690
+ async function handleToggleAutoSync(instances) {
8691
+ if (instances.length === 0) {
8692
+ console.log(source_default.yellow("No instances found."));
8693
+ return;
8694
+ }
8695
+ console.log(source_default.bold(`
8696
+ \uD83D\uDD04 Toggle Auto-Sync
8697
+ `));
8698
+ const { instanceName } = await import_prompts.default({
8699
+ type: "select",
8700
+ name: "instanceName",
8701
+ message: "Select an instance:",
8702
+ choices: instances.map((instance2) => {
8703
+ const currentStatus2 = instance2.autoSync !== false ? "on" : "off";
8704
+ return {
8705
+ title: `${instance2.name} (auto-sync: ${currentStatus2})`,
8706
+ value: instance2.name
8707
+ };
8708
+ })
8709
+ });
8710
+ if (!instanceName)
8711
+ return;
8712
+ const instance = instances.find((i) => i.name === instanceName);
8713
+ if (!instance)
8714
+ return;
8715
+ const currentStatus = instance.autoSync !== false;
8716
+ const { action } = await import_prompts.default({
8717
+ type: "select",
8718
+ name: "action",
8719
+ message: `Auto-sync is currently ${source_default.cyan(currentStatus ? "enabled" : "disabled")}. What would you like to do?`,
8720
+ choices: [
8721
+ { title: currentStatus ? "Turn off (copy files locally)" : "Turn on (use symlinks)", value: "toggle" },
8722
+ { title: "Cancel", value: "cancel" }
8723
+ ],
8724
+ initial: 0
8725
+ });
8726
+ if (!action || action === "cancel") {
8727
+ console.log(source_default.yellow("\u2717 Cancelled"));
8728
+ return;
8729
+ }
8730
+ const newStatus = !currentStatus;
8731
+ console.log(source_default.bold(`
8732
+ \uD83D\uDD04 ${newStatus ? "Enabling" : "Disabling"} auto-sync for '${instanceName}'...
8733
+ `));
8734
+ try {
8735
+ await updateInstanceAutoSync(instanceName, newStatus);
8736
+ if (newStatus) {
8737
+ await syncPluginsAndSkills(instance.configDir);
8738
+ } else {
8739
+ await unsyncPluginsAndSkills(instance.configDir);
8740
+ }
8741
+ console.log(source_default.green(`
8742
+ \u2713 Auto-sync ${newStatus ? "enabled" : "disabled"} for '${instanceName}'`));
8743
+ } catch (error) {
8744
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
8745
+ }
8746
+ }
8747
+ async function handleManagePlugins(instances) {
8748
+ if (instances.length === 0) {
8749
+ console.log(source_default.yellow("No instances found."));
8750
+ return;
8751
+ }
8752
+ console.log(source_default.bold(`
8753
+ \uD83D\uDD0C Manage Plugins
8754
+ `));
8755
+ const { action } = await import_prompts.default({
8756
+ type: "select",
8757
+ name: "action",
8758
+ message: "What would you like to do?",
8759
+ choices: [
8760
+ { title: "\uD83D\uDCCB List all plugins", value: "list" },
8761
+ { title: "\uD83D\uDCCB Plugins for instance", value: "instance" },
8762
+ { title: "\u2705 Enable plugins", value: "enable" },
8763
+ { title: "\u274C Disable plugins", value: "disable" },
8764
+ { title: "\uD83D\uDCCB Copy from default", value: "copy" },
8765
+ { title: "Cancel", value: "cancel" }
8766
+ ],
8767
+ initial: 0
8768
+ });
8769
+ if (!action || action === "cancel") {
8770
+ console.log(source_default.yellow("\u2717 Cancelled"));
8771
+ return;
8772
+ }
8773
+ switch (action) {
8774
+ case "list":
8775
+ await handlePluginsList("");
8776
+ break;
8777
+ case "instance":
8778
+ const { listInstance } = await import_prompts.default({
8779
+ type: "select",
8780
+ name: "listInstance",
8781
+ message: "Select an instance:",
8782
+ choices: instances.map((i) => ({ title: i.name, value: i.name }))
8783
+ });
8784
+ if (listInstance) {
8785
+ await handlePluginsList(listInstance);
8786
+ }
8787
+ break;
8788
+ case "enable": {
8789
+ const { enableInstance } = await import_prompts.default({
8790
+ type: "select",
8791
+ name: "enableInstance",
8792
+ message: "Select an instance:",
8793
+ choices: instances.map((i) => ({ title: i.name, value: i.name }))
8794
+ });
8795
+ if (!enableInstance)
8796
+ return;
8797
+ const plugins = await getEnabledPlugins(instances.find((i) => i.name === enableInstance).configDir);
8798
+ if (!plugins) {
8799
+ console.log(source_default.yellow("No plugins configured for this instance"));
8800
+ return;
8801
+ }
8802
+ const { pluginsToEnable } = await import_prompts.default({
8803
+ type: "multiselect",
8804
+ name: "pluginsToEnable",
8805
+ message: "Select plugins to enable:",
8806
+ choices: Object.entries(plugins).filter(([_, enabled]) => !enabled).map(([pluginId, _]) => ({ title: pluginId, value: pluginId }))
8807
+ });
8808
+ if (pluginsToEnable && pluginsToEnable.length > 0) {
8809
+ await handlePluginsEnable(enableInstance, pluginsToEnable);
8810
+ }
8811
+ break;
8812
+ }
8813
+ case "disable": {
8814
+ const { disableInstance } = await import_prompts.default({
8815
+ type: "select",
8816
+ name: "disableInstance",
8817
+ message: "Select an instance:",
8818
+ choices: instances.map((i) => ({ title: i.name, value: i.name }))
8819
+ });
8820
+ if (!disableInstance)
8821
+ return;
8822
+ const plugins = await getEnabledPlugins(instances.find((i) => i.name === disableInstance).configDir);
8823
+ if (!plugins) {
8824
+ console.log(source_default.yellow("No plugins configured for this instance"));
8825
+ return;
8826
+ }
8827
+ const { pluginsToDisable } = await import_prompts.default({
8828
+ type: "multiselect",
8829
+ name: "pluginsToDisable",
8830
+ message: "Select plugins to disable:",
8831
+ choices: Object.entries(plugins).filter(([_, enabled]) => enabled).map(([pluginId, _]) => ({ title: pluginId, value: pluginId }))
8832
+ });
8833
+ if (pluginsToDisable && pluginsToDisable.length > 0) {
8834
+ await handlePluginsDisable(disableInstance, pluginsToDisable);
8835
+ }
8836
+ break;
8837
+ }
8838
+ case "copy": {
8839
+ const { copyInstance } = await import_prompts.default({
8840
+ type: "select",
8841
+ name: "copyInstance",
8842
+ message: "Select an instance:",
8843
+ choices: instances.map((i) => ({ title: i.name, value: i.name }))
8844
+ });
8845
+ if (copyInstance) {
8846
+ await handlePluginsCopy(copyInstance);
8847
+ }
8848
+ break;
8849
+ }
8850
+ }
8851
+ }
8270
8852
  program2.command("mcp").description("Manage MCP server configurations").argument("[action]", "Action to perform (list, copy, verify)", "list").argument("[instance]", "Instance name (for list/verify)", "").argument("[source]", "Source instance name (for copy)", "").argument("[target]", "Target instance name (for copy)", "").action(async (action = "list", instance = "", source = "", target = "") => {
8271
8853
  try {
8272
8854
  switch (action) {
@@ -8280,12 +8862,12 @@ program2.command("mcp").description("Manage MCP server configurations").argument
8280
8862
  await handleMcpVerify(instance);
8281
8863
  break;
8282
8864
  default:
8283
- console.error(source_default.red(`✗ Unknown action: ${action}`));
8865
+ console.error(source_default.red(`\u2717 Unknown action: ${action}`));
8284
8866
  console.log(source_default.gray("Available actions: list, copy, verify"));
8285
8867
  process.exit(1);
8286
8868
  }
8287
8869
  } catch (error) {
8288
- console.error(source_default.red(`✗ Error: ${error.message}`));
8870
+ console.error(source_default.red(`\u2717 Error: ${error.message}`));
8289
8871
  process.exit(1);
8290
8872
  }
8291
8873
  });
@@ -8303,11 +8885,11 @@ async function handleMcpList(instanceName) {
8303
8885
  for (const instance of instances) {
8304
8886
  const mcpServers = await listMcpServers(instance.name);
8305
8887
  const hasMcp = mcpServers && Object.keys(mcpServers).length > 0;
8306
- console.log(source_default.cyan(`● ${instance.name}`));
8888
+ console.log(source_default.cyan(`\u25CF ${instance.name}`));
8307
8889
  console.log(source_default.gray(` MCP Servers: ${hasMcp ? Object.keys(mcpServers).length : "None"}`));
8308
8890
  if (hasMcp) {
8309
8891
  for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
8310
- console.log(source_default.gray(` • ${serverName} (${serverConfig.type})`));
8892
+ console.log(source_default.gray(` \u2022 ${serverName} (${serverConfig.type})`));
8311
8893
  }
8312
8894
  }
8313
8895
  console.log();
@@ -8322,7 +8904,7 @@ async function handleMcpList(instanceName) {
8322
8904
  \uD83D\uDCCB MCP Servers in '${instanceName}'
8323
8905
  `));
8324
8906
  for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
8325
- console.log(source_default.cyan(`● ${serverName}`));
8907
+ console.log(source_default.cyan(`\u25CF ${serverName}`));
8326
8908
  console.log(source_default.gray(` Type: ${serverConfig.type}`));
8327
8909
  if (serverConfig.command) {
8328
8910
  console.log(source_default.gray(` Command: ${serverConfig.command}`));
@@ -8385,7 +8967,7 @@ async function handleMcpCopy(sourceInstanceName, targetInstanceName) {
8385
8967
  \uD83D\uDD04 Copying MCP servers from '${source}' to '${target}'
8386
8968
  `));
8387
8969
  await copyMcpServersBetweenInstances(source, target);
8388
- console.log(source_default.green(`✓ MCP servers copied successfully!`));
8970
+ console.log(source_default.green(`\u2713 MCP servers copied successfully!`));
8389
8971
  }
8390
8972
  async function handleMcpVerify(instanceName) {
8391
8973
  const instances = await listInstances();
@@ -8413,20 +8995,48 @@ async function handleMcpVerify(instanceName) {
8413
8995
  `));
8414
8996
  const mcpServers = await listMcpServers(instance);
8415
8997
  if (!mcpServers) {
8416
- console.log(source_default.yellow("⚠ No MCP configuration found"));
8998
+ console.log(source_default.yellow("\u26A0 No MCP configuration found"));
8417
8999
  return;
8418
9000
  }
8419
9001
  const serverCount = Object.keys(mcpServers).length;
8420
- console.log(source_default.green(`✓ Found ${serverCount} MCP server(s)`));
9002
+ console.log(source_default.green(`\u2713 Found ${serverCount} MCP server(s)`));
8421
9003
  for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
8422
- console.log(source_default.gray(` • ${serverName}: ${serverConfig.type}`));
9004
+ console.log(source_default.gray(` \u2022 ${serverName}: ${serverConfig.type}`));
8423
9005
  if (serverConfig.type === "stdio" && !serverConfig.command) {
8424
- console.log(source_default.yellow(` ⚠ Missing command for stdio server`));
9006
+ console.log(source_default.yellow(` \u26A0 Missing command for stdio server`));
8425
9007
  } else if ((serverConfig.type === "http" || serverConfig.type === "sse") && !serverConfig.url) {
8426
- console.log(source_default.yellow(` ⚠ Missing URL for ${serverConfig.type} server`));
9008
+ console.log(source_default.yellow(` \u26A0 Missing URL for ${serverConfig.type} server`));
8427
9009
  } else {
8428
- console.log(source_default.green(` ✓ Configuration looks valid`));
9010
+ console.log(source_default.green(` \u2713 Configuration looks valid`));
8429
9011
  }
8430
9012
  }
8431
9013
  }
8432
9014
  program2.parse();
9015
+ async function runUpdateCheck() {
9016
+ try {
9017
+ const updateInfo = await checkForClaudeMultiUpdates();
9018
+ if (updateInfo.updateAvailable) {
9019
+ console.log();
9020
+ console.log(source_default.yellow(`New version available: claude-multi ${updateInfo.latest}`));
9021
+ console.log(source_default.gray(`Current version: ${updateInfo.current}`));
9022
+ console.log();
9023
+ const { shouldUpdate } = await import_prompts.default({
9024
+ type: "confirm",
9025
+ name: "shouldUpdate",
9026
+ message: "Would you like to update now?",
9027
+ initial: true
9028
+ });
9029
+ if (shouldUpdate) {
9030
+ console.log(source_default.cyan(`
9031
+ Updating claude-multi...
9032
+ `));
9033
+ upgradeClaudeMulti();
9034
+ console.log(source_default.green(`
9035
+ Update complete! Please run your command again.
9036
+ `));
9037
+ process.exit(0);
9038
+ }
9039
+ }
9040
+ } catch {}
9041
+ }
9042
+ runUpdateCheck().catch(() => {});