claude-multi 0.4.3 → 0.4.4
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 +84 -58
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7289,7 +7289,7 @@ import { join as join3 } from "path";
|
|
|
7289
7289
|
import { existsSync, mkdirSync, readdirSync, statSync, readlinkSync, rmSync, lstatSync } from "fs";
|
|
7290
7290
|
import { readFile, writeFile, copyFile, mkdir, symlink } from "fs/promises";
|
|
7291
7291
|
import { homedir } from "os";
|
|
7292
|
-
import { join } from "path";
|
|
7292
|
+
import { dirname, isAbsolute, join, relative, resolve } from "path";
|
|
7293
7293
|
|
|
7294
7294
|
// src/templates.ts
|
|
7295
7295
|
var PROVIDER_TEMPLATES = {
|
|
@@ -7353,6 +7353,56 @@ function applyProviderTemplate(template, apiKey) {
|
|
|
7353
7353
|
// src/config.ts
|
|
7354
7354
|
var CONFIG_DIR = join(homedir(), ".claude-multi");
|
|
7355
7355
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7356
|
+
var SYNC_DIRS = ["plugins", "skills"];
|
|
7357
|
+
function lstatSafe(path) {
|
|
7358
|
+
try {
|
|
7359
|
+
return lstatSync(path);
|
|
7360
|
+
} catch {
|
|
7361
|
+
return null;
|
|
7362
|
+
}
|
|
7363
|
+
}
|
|
7364
|
+
function resolveSymlinkTarget(symlinkPath) {
|
|
7365
|
+
try {
|
|
7366
|
+
const link = readlinkSync(symlinkPath);
|
|
7367
|
+
if (isAbsolute(link))
|
|
7368
|
+
return link;
|
|
7369
|
+
return resolve(dirname(symlinkPath), link);
|
|
7370
|
+
} catch {
|
|
7371
|
+
return null;
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
async function ensureDirSymlink(params) {
|
|
7375
|
+
const { configDir, sourceBaseDir, dir, logLabel } = params;
|
|
7376
|
+
const targetPath = join(configDir, dir);
|
|
7377
|
+
const sourcePath = join(sourceBaseDir, dir);
|
|
7378
|
+
if (!existsSync(sourcePath)) {
|
|
7379
|
+
if (logLabel) {
|
|
7380
|
+
console.log(source_default.yellow(` \u26A0 Source ${dir} not found in ${logLabel}, skipping`));
|
|
7381
|
+
}
|
|
7382
|
+
return "skipped";
|
|
7383
|
+
}
|
|
7384
|
+
const existing = lstatSafe(targetPath);
|
|
7385
|
+
if (existing) {
|
|
7386
|
+
if (existing.isSymbolicLink()) {
|
|
7387
|
+
const resolvedExisting = resolveSymlinkTarget(targetPath);
|
|
7388
|
+
if (resolvedExisting === sourcePath) {
|
|
7389
|
+
if (logLabel)
|
|
7390
|
+
console.log(source_default.gray(` \u2713 ${dir} already synced`));
|
|
7391
|
+
return "already";
|
|
7392
|
+
}
|
|
7393
|
+
rmSync(targetPath, { force: true });
|
|
7394
|
+
} else {
|
|
7395
|
+
rmSync(targetPath, { force: true, recursive: true });
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
7398
|
+
const linkTarget = relative(configDir, sourcePath);
|
|
7399
|
+
const type = process.platform === "win32" ? "junction" : "dir";
|
|
7400
|
+
await symlink(linkTarget, targetPath, type);
|
|
7401
|
+
if (logLabel) {
|
|
7402
|
+
console.log(source_default.green(` \u2713 Symlinked: ${dir} -> ${logLabel}/${dir}`));
|
|
7403
|
+
}
|
|
7404
|
+
return "linked";
|
|
7405
|
+
}
|
|
7356
7406
|
function ensureConfigDir() {
|
|
7357
7407
|
if (!existsSync(CONFIG_DIR)) {
|
|
7358
7408
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -7428,7 +7478,6 @@ function isBrokenSymlink(path) {
|
|
|
7428
7478
|
const target = readlinkSync(path);
|
|
7429
7479
|
if (!target)
|
|
7430
7480
|
return true;
|
|
7431
|
-
const { resolve, dirname, isAbsolute } = __require("path");
|
|
7432
7481
|
let targetPath;
|
|
7433
7482
|
if (isAbsolute(target)) {
|
|
7434
7483
|
targetPath = target;
|
|
@@ -7441,9 +7490,8 @@ function isBrokenSymlink(path) {
|
|
|
7441
7490
|
}
|
|
7442
7491
|
}
|
|
7443
7492
|
function detectBrokenSymlinks(configDir) {
|
|
7444
|
-
const symlinkDirs = ["plugins", "skills"];
|
|
7445
7493
|
const result = { broken: [], all: [] };
|
|
7446
|
-
for (const dir of
|
|
7494
|
+
for (const dir of SYNC_DIRS) {
|
|
7447
7495
|
const targetPath = join(configDir, dir);
|
|
7448
7496
|
try {
|
|
7449
7497
|
const stats = lstatSync(targetPath);
|
|
@@ -7493,7 +7541,6 @@ async function copyAllFromDefault(targetConfigDir, autoSync = true) {
|
|
|
7493
7541
|
if (!existsSync(targetConfigDir)) {
|
|
7494
7542
|
await mkdir(targetConfigDir, { recursive: true });
|
|
7495
7543
|
}
|
|
7496
|
-
const symlinkDirs = ["plugins", "skills"];
|
|
7497
7544
|
const excludeFiles = [
|
|
7498
7545
|
"config.json",
|
|
7499
7546
|
".config.json",
|
|
@@ -7518,25 +7565,12 @@ async function copyAllFromDefault(targetConfigDir, autoSync = true) {
|
|
|
7518
7565
|
continue;
|
|
7519
7566
|
}
|
|
7520
7567
|
if (stat.isDirectory()) {
|
|
7521
|
-
if (autoSync &&
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
if (currentTarget === absoluteSource2) {
|
|
7528
|
-
console.log(` \u2713 Already symlinked: ${entry}`);
|
|
7529
|
-
continue;
|
|
7530
|
-
}
|
|
7531
|
-
console.log(source_default.yellow(` \u26A0 Replacing incorrect symlink: ${entry}`));
|
|
7532
|
-
} else {
|
|
7533
|
-
console.log(source_default.yellow(` \u26A0 Removing existing directory: ${entry}`));
|
|
7534
|
-
}
|
|
7535
|
-
rmSync(targetPath, { force: true, recursive: true });
|
|
7536
|
-
}
|
|
7537
|
-
const absoluteSource = join(homedir(), ".claude", entry);
|
|
7538
|
-
await symlink(absoluteSource, targetPath, "dir");
|
|
7539
|
-
console.log(` Symlinked: ${entry} -> ~/.claude/${entry}`);
|
|
7568
|
+
if (autoSync && SYNC_DIRS.includes(entry)) {
|
|
7569
|
+
await ensureDirSymlink({
|
|
7570
|
+
configDir: target,
|
|
7571
|
+
sourceBaseDir: defaultDir,
|
|
7572
|
+
dir: entry
|
|
7573
|
+
});
|
|
7540
7574
|
} else {
|
|
7541
7575
|
if (!existsSync(targetPath)) {
|
|
7542
7576
|
await mkdir(targetPath, { recursive: true });
|
|
@@ -7653,46 +7687,28 @@ async function createSettingsFromTemplate(targetConfigDir, template, apiKey) {
|
|
|
7653
7687
|
}
|
|
7654
7688
|
async function syncPluginsAndSkills(configDir) {
|
|
7655
7689
|
const defaultDir = getDefaultClaudeDir();
|
|
7656
|
-
const symlinkDirs = ["plugins", "skills"];
|
|
7657
7690
|
if (!existsSync(configDir)) {
|
|
7658
7691
|
throw new Error("Instance config directory does not exist");
|
|
7659
7692
|
}
|
|
7660
|
-
for (const dir of
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
}
|
|
7667
|
-
if (existsSync(targetPath)) {
|
|
7668
|
-
try {
|
|
7669
|
-
const linkTarget = readlinkSync(targetPath);
|
|
7670
|
-
const expectedTarget = join("..", "..", ".claude", dir);
|
|
7671
|
-
if (linkTarget === expectedTarget || linkTarget === sourcePath) {
|
|
7672
|
-
console.log(source_default.gray(` \u2713 ${dir} already synced`));
|
|
7673
|
-
continue;
|
|
7674
|
-
}
|
|
7675
|
-
rmSync(targetPath, { force: true });
|
|
7676
|
-
} catch {
|
|
7677
|
-
rmSync(targetPath, { force: true, recursive: true });
|
|
7678
|
-
}
|
|
7679
|
-
}
|
|
7680
|
-
const relativePath = join("..", "..", ".claude", dir);
|
|
7681
|
-
await symlink(relativePath, targetPath, "dir");
|
|
7682
|
-
console.log(source_default.green(` \u2713 Symlinked: ${dir} -> ~/.claude/${dir}`));
|
|
7693
|
+
for (const dir of SYNC_DIRS) {
|
|
7694
|
+
await ensureDirSymlink({
|
|
7695
|
+
configDir,
|
|
7696
|
+
sourceBaseDir: defaultDir,
|
|
7697
|
+
dir,
|
|
7698
|
+
logLabel: defaultDir
|
|
7699
|
+
});
|
|
7683
7700
|
}
|
|
7684
7701
|
}
|
|
7685
7702
|
async function unsyncPluginsAndSkills(configDir) {
|
|
7686
7703
|
const defaultDir = getDefaultClaudeDir();
|
|
7687
|
-
const symlinkDirs = ["plugins", "skills"];
|
|
7688
7704
|
if (!existsSync(configDir)) {
|
|
7689
7705
|
throw new Error("Instance config directory does not exist");
|
|
7690
7706
|
}
|
|
7691
|
-
for (const dir of
|
|
7707
|
+
for (const dir of SYNC_DIRS) {
|
|
7692
7708
|
const targetPath = join(configDir, dir);
|
|
7693
7709
|
const sourcePath = join(defaultDir, dir);
|
|
7694
7710
|
if (!existsSync(sourcePath)) {
|
|
7695
|
-
console.log(source_default.yellow(` \u26A0 Source ${dir} not found in
|
|
7711
|
+
console.log(source_default.yellow(` \u26A0 Source ${dir} not found in ${defaultDir}, skipping`));
|
|
7696
7712
|
continue;
|
|
7697
7713
|
}
|
|
7698
7714
|
let isSymlink = false;
|
|
@@ -7779,7 +7795,7 @@ async function listAvailablePlugins() {
|
|
|
7779
7795
|
// src/wrapper.ts
|
|
7780
7796
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync as unlinkSync2 } from "fs";
|
|
7781
7797
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
7782
|
-
import { resolve, dirname } from "path";
|
|
7798
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
7783
7799
|
import { execSync } from "child_process";
|
|
7784
7800
|
function getClaudePath() {
|
|
7785
7801
|
try {
|
|
@@ -7829,7 +7845,7 @@ set "CLAUDE_CONFIG_DIR=${options.configDir}"
|
|
|
7829
7845
|
}
|
|
7830
7846
|
async function createWrapper(options) {
|
|
7831
7847
|
const { binaryPath, configDir } = options;
|
|
7832
|
-
const binDir =
|
|
7848
|
+
const binDir = dirname2(binaryPath);
|
|
7833
7849
|
if (!existsSync2(binDir)) {
|
|
7834
7850
|
mkdirSync2(binDir, { recursive: true });
|
|
7835
7851
|
}
|
|
@@ -7851,9 +7867,9 @@ function removeWrapper(binaryPath) {
|
|
|
7851
7867
|
}
|
|
7852
7868
|
}
|
|
7853
7869
|
function getDefaultBinaryPath(name) {
|
|
7854
|
-
const binDir = process.platform === "win32" ?
|
|
7870
|
+
const binDir = process.platform === "win32" ? resolve2(process.env.LOCALAPPDATA || "", "bun", "bin") : resolve2(process.env.HOME || "", ".local", "bin");
|
|
7855
7871
|
const binaryName = process.platform === "win32" ? `claude-${name}.cmd` : `claude-${name}`;
|
|
7856
|
-
return
|
|
7872
|
+
return resolve2(binDir, binaryName);
|
|
7857
7873
|
}
|
|
7858
7874
|
|
|
7859
7875
|
// src/version.ts
|
|
@@ -8817,6 +8833,7 @@ async function handleToggleAutoSync(instances) {
|
|
|
8817
8833
|
message: `Auto-sync is currently ${source_default.cyan(currentStatus ? "enabled" : "disabled")}. What would you like to do?`,
|
|
8818
8834
|
choices: [
|
|
8819
8835
|
{ title: currentStatus ? "Turn off (copy files locally)" : "Turn on (use symlinks)", value: "toggle" },
|
|
8836
|
+
...currentStatus ? [{ title: "Force re-sync now (rebuild symlinks)", value: "force-sync" }] : [],
|
|
8820
8837
|
{ title: "Cancel", value: "cancel" }
|
|
8821
8838
|
],
|
|
8822
8839
|
initial: 0
|
|
@@ -8825,11 +8842,20 @@ async function handleToggleAutoSync(instances) {
|
|
|
8825
8842
|
console.log(source_default.yellow("\u2717 Cancelled"));
|
|
8826
8843
|
return;
|
|
8827
8844
|
}
|
|
8828
|
-
|
|
8829
|
-
|
|
8845
|
+
try {
|
|
8846
|
+
if (action === "force-sync") {
|
|
8847
|
+
console.log(source_default.bold(`
|
|
8848
|
+
\uD83D\uDD04 Re-syncing symlinks for '${instanceName}'...
|
|
8849
|
+
`));
|
|
8850
|
+
await syncPluginsAndSkills(instance.configDir);
|
|
8851
|
+
console.log(source_default.green(`
|
|
8852
|
+
\u2713 Symlinks rebuilt for '${instanceName}'`));
|
|
8853
|
+
return;
|
|
8854
|
+
}
|
|
8855
|
+
const newStatus = !currentStatus;
|
|
8856
|
+
console.log(source_default.bold(`
|
|
8830
8857
|
\uD83D\uDD04 ${newStatus ? "Enabling" : "Disabling"} auto-sync for '${instanceName}'...
|
|
8831
8858
|
`));
|
|
8832
|
-
try {
|
|
8833
8859
|
await updateInstanceAutoSync(instanceName, newStatus);
|
|
8834
8860
|
if (newStatus) {
|
|
8835
8861
|
await syncPluginsAndSkills(instance.configDir);
|