claude-multi 0.2.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/CHANGELOG.md +55 -0
- package/README.md +70 -81
- package/development.md +602 -0
- package/dist/cli.js +877 -127
- package/package.json +3 -3
- package/bun.lock +0 -77
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
|
|
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 =
|
|
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
|
|
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("
|
|
601
|
-
var childProcess = __require("
|
|
602
|
-
var path = __require("
|
|
603
|
-
var fs = __require("
|
|
604
|
-
var process2 = __require("
|
|
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 "
|
|
6973
|
-
import os from "
|
|
6974
|
-
import tty from "
|
|
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,14 +7282,75 @@ 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 "
|
|
7286
|
-
import { join as
|
|
7285
|
+
import { homedir as homedir2 } from "os";
|
|
7286
|
+
import { join as join3 } from "path";
|
|
7287
|
+
|
|
7288
|
+
// src/config.ts
|
|
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
|
+
|
|
7294
|
+
// src/templates.ts
|
|
7295
|
+
var PROVIDER_TEMPLATES = {
|
|
7296
|
+
glm: {
|
|
7297
|
+
name: "glm",
|
|
7298
|
+
displayName: "GLM (\u667A\u8C31AI)",
|
|
7299
|
+
description: "GLM-4.5 and GLM-4.7 models via z.ai",
|
|
7300
|
+
settings: {
|
|
7301
|
+
env: {
|
|
7302
|
+
ANTHROPIC_AUTH_TOKEN: "",
|
|
7303
|
+
ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
|
|
7304
|
+
API_TIMEOUT_MS: "3000000",
|
|
7305
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.5-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
|
+
ENABLE_THINKING: "true",
|
|
7311
|
+
REASONING_EFFORT: "high",
|
|
7312
|
+
MAX_THINKING_TOKENS: "8000",
|
|
7313
|
+
ENABLE_STREAMING: "true",
|
|
7314
|
+
MAX_OUTPUT_TOKENS: "64000"
|
|
7315
|
+
},
|
|
7316
|
+
includeCoAuthoredBy: false,
|
|
7317
|
+
alwaysThinkingEnabled: false
|
|
7318
|
+
}
|
|
7319
|
+
},
|
|
7320
|
+
minimax: {
|
|
7321
|
+
name: "minimax",
|
|
7322
|
+
displayName: "MiniMax",
|
|
7323
|
+
description: "MiniMax-M2 model via minimax.io",
|
|
7324
|
+
settings: {
|
|
7325
|
+
env: {
|
|
7326
|
+
ANTHROPIC_AUTH_TOKEN: "",
|
|
7327
|
+
ANTHROPIC_BASE_URL: "https://api.minimax.io/anthropic",
|
|
7328
|
+
API_TIMEOUT_MS: "3000000",
|
|
7329
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
7330
|
+
ANTHROPIC_MODEL: "MiniMax-M2",
|
|
7331
|
+
ANTHROPIC_SMALL_FAST_MODEL: "MiniMax-M2",
|
|
7332
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2",
|
|
7333
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2",
|
|
7334
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2"
|
|
7335
|
+
},
|
|
7336
|
+
includeCoAuthoredBy: false,
|
|
7337
|
+
alwaysThinkingEnabled: true
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
};
|
|
7341
|
+
function getAvailableProviders() {
|
|
7342
|
+
return Object.values(PROVIDER_TEMPLATES);
|
|
7343
|
+
}
|
|
7344
|
+
function getProviderTemplate(name) {
|
|
7345
|
+
return PROVIDER_TEMPLATES[name.toLowerCase()];
|
|
7346
|
+
}
|
|
7347
|
+
function applyProviderTemplate(template, apiKey) {
|
|
7348
|
+
const settings = JSON.parse(JSON.stringify(template.settings));
|
|
7349
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
7350
|
+
return settings;
|
|
7351
|
+
}
|
|
7287
7352
|
|
|
7288
7353
|
// 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";
|
|
7293
7354
|
var CONFIG_DIR = join(homedir(), ".claude-multi");
|
|
7294
7355
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7295
7356
|
function ensureConfigDir() {
|
|
@@ -7342,7 +7403,21 @@ async function listInstances() {
|
|
|
7342
7403
|
const config = await loadConfig();
|
|
7343
7404
|
return config.instances;
|
|
7344
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;
|
|
7345
7417
|
function getDefaultClaudeDir() {
|
|
7418
|
+
if (_testDefaultClaudeDir) {
|
|
7419
|
+
return _testDefaultClaudeDir;
|
|
7420
|
+
}
|
|
7346
7421
|
return join(homedir(), ".claude");
|
|
7347
7422
|
}
|
|
7348
7423
|
function hasDefaultClaudeConfig() {
|
|
@@ -7353,16 +7428,29 @@ function hasDefaultClaudeConfig() {
|
|
|
7353
7428
|
async function copySettingsFromDefault(targetConfigDir) {
|
|
7354
7429
|
const defaultDir = getDefaultClaudeDir();
|
|
7355
7430
|
const sourceSettings = join(defaultDir, "settings.json");
|
|
7356
|
-
const targetSettings = join(targetConfigDir, "settings.json");
|
|
7357
7431
|
if (!existsSync(sourceSettings)) {
|
|
7358
7432
|
throw new Error("Default Claude settings.json not found");
|
|
7359
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
|
+
}
|
|
7360
7447
|
if (!existsSync(targetConfigDir)) {
|
|
7361
7448
|
await mkdir(targetConfigDir, { recursive: true });
|
|
7362
7449
|
}
|
|
7363
|
-
|
|
7450
|
+
const targetSettings = join(targetConfigDir, "settings.json");
|
|
7451
|
+
await writeFile(targetSettings, JSON.stringify(safeSettings, null, 2), "utf-8");
|
|
7364
7452
|
}
|
|
7365
|
-
async function copyAllFromDefault(targetConfigDir) {
|
|
7453
|
+
async function copyAllFromDefault(targetConfigDir, autoSync = true) {
|
|
7366
7454
|
const defaultDir = getDefaultClaudeDir();
|
|
7367
7455
|
if (!existsSync(defaultDir)) {
|
|
7368
7456
|
throw new Error("Default Claude directory not found");
|
|
@@ -7370,6 +7458,7 @@ async function copyAllFromDefault(targetConfigDir) {
|
|
|
7370
7458
|
if (!existsSync(targetConfigDir)) {
|
|
7371
7459
|
await mkdir(targetConfigDir, { recursive: true });
|
|
7372
7460
|
}
|
|
7461
|
+
const symlinkDirs = ["plugins", "skills"];
|
|
7373
7462
|
const excludeFiles = [
|
|
7374
7463
|
"config.json",
|
|
7375
7464
|
".config.json",
|
|
@@ -7394,10 +7483,24 @@ async function copyAllFromDefault(targetConfigDir) {
|
|
|
7394
7483
|
continue;
|
|
7395
7484
|
}
|
|
7396
7485
|
if (stat.isDirectory()) {
|
|
7397
|
-
if (
|
|
7398
|
-
|
|
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);
|
|
7399
7503
|
}
|
|
7400
|
-
await copyRecursive(sourcePath, targetPath);
|
|
7401
7504
|
} else {
|
|
7402
7505
|
await copyFile(sourcePath, targetPath);
|
|
7403
7506
|
}
|
|
@@ -7498,12 +7601,144 @@ async function listMcpServers(instanceName) {
|
|
|
7498
7601
|
const mcpConfig = await detectMcpConfigurations(instance.configDir);
|
|
7499
7602
|
return mcpConfig?.mcpServers || null;
|
|
7500
7603
|
}
|
|
7604
|
+
async function createSettingsFromTemplate(targetConfigDir, template, apiKey) {
|
|
7605
|
+
if (!existsSync(targetConfigDir)) {
|
|
7606
|
+
await mkdir(targetConfigDir, { recursive: true });
|
|
7607
|
+
}
|
|
7608
|
+
const settings = applyProviderTemplate(template, apiKey);
|
|
7609
|
+
const settingsFile = join(targetConfigDir, "settings.json");
|
|
7610
|
+
await writeFile(settingsFile, JSON.stringify(settings, null, 2), "utf-8");
|
|
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
|
+
}
|
|
7501
7736
|
|
|
7502
7737
|
// src/wrapper.ts
|
|
7503
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync } from "
|
|
7504
|
-
import { writeFile as writeFile2 } from "
|
|
7505
|
-
import { resolve, dirname } from "
|
|
7506
|
-
import { execSync } from "
|
|
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";
|
|
7507
7742
|
function getClaudePath() {
|
|
7508
7743
|
try {
|
|
7509
7744
|
const command = process.platform === "win32" ? "where claude" : "which claude";
|
|
@@ -7520,7 +7755,7 @@ function getClaudePath() {
|
|
|
7520
7755
|
}
|
|
7521
7756
|
function generateWrapperScript(options) {
|
|
7522
7757
|
const claudePath = getClaudePath();
|
|
7523
|
-
return `#!/usr/bin/env
|
|
7758
|
+
return `#!/usr/bin/env bun
|
|
7524
7759
|
|
|
7525
7760
|
// Claude Multi - Wrapper for ${options.name}
|
|
7526
7761
|
// Generated by claude-multi
|
|
@@ -7570,20 +7805,48 @@ async function createWrapper(options) {
|
|
|
7570
7805
|
}
|
|
7571
7806
|
function removeWrapper(binaryPath) {
|
|
7572
7807
|
if (existsSync2(binaryPath)) {
|
|
7573
|
-
|
|
7808
|
+
unlinkSync2(binaryPath);
|
|
7574
7809
|
}
|
|
7575
7810
|
}
|
|
7576
7811
|
function getDefaultBinaryPath(name) {
|
|
7577
|
-
const binDir = process.platform === "win32" ? resolve(process.env.
|
|
7812
|
+
const binDir = process.platform === "win32" ? resolve(process.env.LOCALAPPDATA || "", "bun", "bin") : resolve(process.env.HOME || "", ".local", "bin");
|
|
7578
7813
|
const binaryName = process.platform === "win32" ? `claude-${name}.cmd` : `claude-${name}`;
|
|
7579
7814
|
return resolve(binDir, binaryName);
|
|
7580
7815
|
}
|
|
7581
7816
|
|
|
7582
7817
|
// src/version.ts
|
|
7583
|
-
import { execSync as execSync2 } from "
|
|
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
|
+
}
|
|
7584
7847
|
function getCurrentVersion() {
|
|
7585
7848
|
try {
|
|
7586
|
-
const output = execSync2("
|
|
7849
|
+
const output = execSync2("bun pm ls -g @anthropic-ai/claude-code --json", {
|
|
7587
7850
|
encoding: "utf-8",
|
|
7588
7851
|
stdio: ["pipe", "pipe", "ignore"]
|
|
7589
7852
|
});
|
|
@@ -7596,7 +7859,7 @@ function getCurrentVersion() {
|
|
|
7596
7859
|
}
|
|
7597
7860
|
async function getLatestVersion() {
|
|
7598
7861
|
try {
|
|
7599
|
-
const output = execSync2("npm view @anthropic-ai/claude-code version", {
|
|
7862
|
+
const output = execSync2("bun pm npm view @anthropic-ai/claude-code version", {
|
|
7600
7863
|
encoding: "utf-8",
|
|
7601
7864
|
stdio: ["pipe", "pipe", "ignore"]
|
|
7602
7865
|
});
|
|
@@ -7617,7 +7880,7 @@ async function checkForUpdates() {
|
|
|
7617
7880
|
async function updateClaudeCode() {
|
|
7618
7881
|
try {
|
|
7619
7882
|
console.log("Updating @anthropic-ai/claude-code...");
|
|
7620
|
-
execSync2("
|
|
7883
|
+
execSync2("bun install -g @anthropic-ai/claude-code@latest", {
|
|
7621
7884
|
stdio: "inherit"
|
|
7622
7885
|
});
|
|
7623
7886
|
console.log("Update completed successfully!");
|
|
@@ -7628,17 +7891,34 @@ async function updateClaudeCode() {
|
|
|
7628
7891
|
|
|
7629
7892
|
// src/cli.ts
|
|
7630
7893
|
var program2 = new Command;
|
|
7631
|
-
program2.name("claude-multi").description("Manage multiple Claude Code instances with different aliases").version("0.
|
|
7632
|
-
program2.command("add <name>").description("Add a new Claude Code instance").option("-c, --config <dir>", "Config directory path", (name) =>
|
|
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) => {
|
|
7633
7896
|
try {
|
|
7634
|
-
const configDir = options.config ||
|
|
7897
|
+
const configDir = options.config || join3(homedir2(), `.claude-${name}`);
|
|
7635
7898
|
const binaryPath = options.binary || getDefaultBinaryPath(name);
|
|
7636
7899
|
const hasDefaultConfig = hasDefaultClaudeConfig();
|
|
7637
7900
|
const hasDefaultMcp = await hasDefaultMcpConfig();
|
|
7638
7901
|
let copySettings = false;
|
|
7639
7902
|
let copyAllFiles = false;
|
|
7640
7903
|
let copyMcpServers = false;
|
|
7641
|
-
|
|
7904
|
+
let useProviderTemplate = false;
|
|
7905
|
+
let providerTemplate = null;
|
|
7906
|
+
let apiKey = "";
|
|
7907
|
+
let autoSync = !options.manual;
|
|
7908
|
+
if (options.provider) {
|
|
7909
|
+
providerTemplate = getProviderTemplate(options.provider);
|
|
7910
|
+
if (!providerTemplate) {
|
|
7911
|
+
console.error(source_default.red(`\u2717 Unknown provider '${options.provider}'. Available: glm, minimax`));
|
|
7912
|
+
process.exit(1);
|
|
7913
|
+
}
|
|
7914
|
+
if (!options.apiKey) {
|
|
7915
|
+
console.error(source_default.red("\u2717 --api-key is required when using --provider"));
|
|
7916
|
+
process.exit(1);
|
|
7917
|
+
}
|
|
7918
|
+
apiKey = options.apiKey;
|
|
7919
|
+
useProviderTemplate = true;
|
|
7920
|
+
}
|
|
7921
|
+
if (options.copySettings || options.copyAll || options.copyMcp || options.skipPrompts || options.provider) {
|
|
7642
7922
|
if (options.copyAll) {
|
|
7643
7923
|
copyAllFiles = true;
|
|
7644
7924
|
copySettings = true;
|
|
@@ -7680,7 +7960,7 @@ Found existing Claude Code configuration at ~/.claude`));
|
|
|
7680
7960
|
]);
|
|
7681
7961
|
if (response.copyOption === undefined) {
|
|
7682
7962
|
console.log(source_default.yellow(`
|
|
7683
|
-
|
|
7963
|
+
\u2717 Cancelled`));
|
|
7684
7964
|
process.exit(0);
|
|
7685
7965
|
}
|
|
7686
7966
|
copySettings = response.copyOption === "settings" || response.copyOption === "settings+mcp" || response.copyOption === "all";
|
|
@@ -7691,28 +7971,39 @@ Found existing Claude Code configuration at ~/.claude`));
|
|
|
7691
7971
|
name,
|
|
7692
7972
|
configDir,
|
|
7693
7973
|
binaryPath,
|
|
7694
|
-
createdAt: new Date().toISOString()
|
|
7974
|
+
createdAt: new Date().toISOString(),
|
|
7975
|
+
autoSync
|
|
7695
7976
|
};
|
|
7696
7977
|
await addInstance(instance);
|
|
7697
7978
|
await createWrapper(instance);
|
|
7698
7979
|
if (copySettings && !copyAllFiles) {
|
|
7699
7980
|
await copySettingsFromDefault(configDir);
|
|
7700
|
-
console.log(source_default.green("
|
|
7981
|
+
console.log(source_default.green("\u2713 Copied settings.json"));
|
|
7701
7982
|
}
|
|
7702
7983
|
if (copyMcpServers && !copyAllFiles) {
|
|
7703
7984
|
try {
|
|
7704
7985
|
await copyMcpServersFromDefault(configDir);
|
|
7705
|
-
console.log(source_default.green("
|
|
7986
|
+
console.log(source_default.green("\u2713 Copied MCP server configurations"));
|
|
7706
7987
|
} catch (error) {
|
|
7707
|
-
console.log(source_default.yellow(
|
|
7988
|
+
console.log(source_default.yellow(`\u26A0 Warning: ${error.message}`));
|
|
7708
7989
|
}
|
|
7709
7990
|
}
|
|
7710
7991
|
if (copyAllFiles) {
|
|
7711
|
-
await copyAllFromDefault(configDir);
|
|
7712
|
-
|
|
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
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
if (useProviderTemplate && providerTemplate && !copySettings && !copyAllFiles) {
|
|
8000
|
+
await createSettingsFromTemplate(configDir, providerTemplate, apiKey);
|
|
8001
|
+
console.log(source_default.green(`\u2713 Applied ${providerTemplate.displayName} template`));
|
|
8002
|
+
} else if (useProviderTemplate && (copySettings || copyAllFiles)) {
|
|
8003
|
+
console.log(source_default.yellow("\u26A0 Provider template skipped (copied settings from default Claude)"));
|
|
7713
8004
|
}
|
|
7714
8005
|
console.log(source_default.green(`
|
|
7715
|
-
|
|
8006
|
+
\u2713 Instance '${name}' created successfully!`));
|
|
7716
8007
|
console.log(source_default.gray(` Binary: ${binaryPath}`));
|
|
7717
8008
|
console.log(source_default.gray(` Config: ${configDir}`));
|
|
7718
8009
|
console.log();
|
|
@@ -7720,7 +8011,7 @@ Found existing Claude Code configuration at ~/.claude`));
|
|
|
7720
8011
|
const pathEnv = process.env.PATH || "";
|
|
7721
8012
|
const isInPath = pathEnv.split(":").some((p) => p === binDir);
|
|
7722
8013
|
if (!isInPath) {
|
|
7723
|
-
console.log(source_default.yellow(
|
|
8014
|
+
console.log(source_default.yellow(`\u26A0 Warning: ${binDir} is not in your PATH`));
|
|
7724
8015
|
console.log(source_default.gray(`Add to PATH by running:`));
|
|
7725
8016
|
console.log(source_default.cyan(` echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc`));
|
|
7726
8017
|
console.log(source_default.cyan(` source ~/.zshrc`));
|
|
@@ -7730,7 +8021,7 @@ Found existing Claude Code configuration at ~/.claude`));
|
|
|
7730
8021
|
console.log(source_default.cyan(`Run: claude-${name} --help`));
|
|
7731
8022
|
}
|
|
7732
8023
|
} catch (error) {
|
|
7733
|
-
console.error(source_default.red(
|
|
8024
|
+
console.error(source_default.red(`\u2717 Error: ${error.message}`));
|
|
7734
8025
|
process.exit(1);
|
|
7735
8026
|
}
|
|
7736
8027
|
});
|
|
@@ -7738,7 +8029,7 @@ program2.command("remove <name>").alias("rm").description("Remove a Claude Code
|
|
|
7738
8029
|
try {
|
|
7739
8030
|
const instance = await getInstance(name);
|
|
7740
8031
|
if (!instance) {
|
|
7741
|
-
console.error(source_default.red(
|
|
8032
|
+
console.error(source_default.red(`\u2717 Instance '${name}' not found`));
|
|
7742
8033
|
process.exit(1);
|
|
7743
8034
|
}
|
|
7744
8035
|
if (!options.force) {
|
|
@@ -7752,11 +8043,11 @@ program2.command("remove <name>").alias("rm").description("Remove a Claude Code
|
|
|
7752
8043
|
}
|
|
7753
8044
|
await removeInstance(name);
|
|
7754
8045
|
removeWrapper(instance.binaryPath);
|
|
7755
|
-
console.log(source_default.green(
|
|
8046
|
+
console.log(source_default.green(`\u2713 Instance '${name}' removed successfully!`));
|
|
7756
8047
|
console.log();
|
|
7757
8048
|
console.log(source_default.gray(`To remove config files, run: rm -rf ${instance.configDir}`));
|
|
7758
8049
|
} catch (error) {
|
|
7759
|
-
console.error(source_default.red(
|
|
8050
|
+
console.error(source_default.red(`\u2717 Error: ${error.message}`));
|
|
7760
8051
|
process.exit(1);
|
|
7761
8052
|
}
|
|
7762
8053
|
});
|
|
@@ -7772,14 +8063,16 @@ program2.command("list").alias("ls").description("List all Claude Code instances
|
|
|
7772
8063
|
console.log(source_default.bold(`Found ${instances.length} instance(s):
|
|
7773
8064
|
`));
|
|
7774
8065
|
for (const instance of instances) {
|
|
7775
|
-
console.log(source_default.cyan(
|
|
8066
|
+
console.log(source_default.cyan(`\u25CF ${instance.name}`));
|
|
7776
8067
|
console.log(source_default.gray(` Binary: ${instance.binaryPath}`));
|
|
7777
8068
|
console.log(source_default.gray(` Config: ${instance.configDir}`));
|
|
7778
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}`));
|
|
7779
8072
|
console.log();
|
|
7780
8073
|
}
|
|
7781
8074
|
} catch (error) {
|
|
7782
|
-
console.error(source_default.red(
|
|
8075
|
+
console.error(source_default.red(`\u2717 Error: ${error.message}`));
|
|
7783
8076
|
process.exit(1);
|
|
7784
8077
|
}
|
|
7785
8078
|
});
|
|
@@ -7787,7 +8080,7 @@ program2.command("info <name>").description("Show details about a specific insta
|
|
|
7787
8080
|
try {
|
|
7788
8081
|
const instance = await getInstance(name);
|
|
7789
8082
|
if (!instance) {
|
|
7790
|
-
console.error(source_default.red(
|
|
8083
|
+
console.error(source_default.red(`\u2717 Instance '${name}' not found`));
|
|
7791
8084
|
process.exit(1);
|
|
7792
8085
|
}
|
|
7793
8086
|
console.log(source_default.bold(`Instance: ${source_default.cyan(instance.name)}
|
|
@@ -7795,8 +8088,10 @@ program2.command("info <name>").description("Show details about a specific insta
|
|
|
7795
8088
|
console.log(`${source_default.gray("Binary:")} ${instance.binaryPath}`);
|
|
7796
8089
|
console.log(`${source_default.gray("Config:")} ${instance.configDir}`);
|
|
7797
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}`);
|
|
7798
8093
|
} catch (error) {
|
|
7799
|
-
console.error(source_default.red(
|
|
8094
|
+
console.error(source_default.red(`\u2717 Error: ${error.message}`));
|
|
7800
8095
|
process.exit(1);
|
|
7801
8096
|
}
|
|
7802
8097
|
});
|
|
@@ -7813,13 +8108,13 @@ program2.command("version").description("Check Claude Code version and updates")
|
|
|
7813
8108
|
console.log(`${source_default.gray("Latest:")} ${source_default.cyan(versionInfo.latest)}`);
|
|
7814
8109
|
console.log();
|
|
7815
8110
|
if (versionInfo.updateAvailable) {
|
|
7816
|
-
console.log(source_default.yellow(
|
|
8111
|
+
console.log(source_default.yellow(`\u26A0 Update available: ${versionInfo.latest}`));
|
|
7817
8112
|
console.log(source_default.gray("Run 'claude-multi update' to update"));
|
|
7818
8113
|
} else if (versionInfo.current) {
|
|
7819
|
-
console.log(source_default.green("
|
|
8114
|
+
console.log(source_default.green("\u2713 You're up to date!"));
|
|
7820
8115
|
}
|
|
7821
8116
|
} catch (error) {
|
|
7822
|
-
console.error(source_default.red(
|
|
8117
|
+
console.error(source_default.red(`\u2717 Error: ${error.message}`));
|
|
7823
8118
|
process.exit(1);
|
|
7824
8119
|
}
|
|
7825
8120
|
});
|
|
@@ -7827,20 +8122,206 @@ program2.command("update").description("Update @anthropic-ai/claude-code to the
|
|
|
7827
8122
|
try {
|
|
7828
8123
|
const versionInfo = await checkForUpdates();
|
|
7829
8124
|
if (!versionInfo.updateAvailable && versionInfo.current) {
|
|
7830
|
-
console.log(source_default.green(
|
|
8125
|
+
console.log(source_default.green(`\u2713 Already up to date (${versionInfo.current})`));
|
|
7831
8126
|
return;
|
|
7832
8127
|
}
|
|
7833
8128
|
await updateClaudeCode();
|
|
7834
8129
|
} catch (error) {
|
|
7835
|
-
console.error(source_default.red(
|
|
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}`));
|
|
7836
8185
|
process.exit(1);
|
|
7837
8186
|
}
|
|
7838
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
|
+
}
|
|
7839
8320
|
program2.command("interactive").alias("i").description("Launch interactive mode for managing Claude Code instances").action(async () => {
|
|
7840
8321
|
try {
|
|
7841
8322
|
await runInteractiveMode();
|
|
7842
8323
|
} catch (error) {
|
|
7843
|
-
console.error(source_default.red(
|
|
8324
|
+
console.error(source_default.red(`\u2717 Error: ${error.message}`));
|
|
7844
8325
|
process.exit(1);
|
|
7845
8326
|
}
|
|
7846
8327
|
});
|
|
@@ -7861,11 +8342,13 @@ async function runInteractiveMode() {
|
|
|
7861
8342
|
name: "action",
|
|
7862
8343
|
message: "What would you like to do?",
|
|
7863
8344
|
choices: [
|
|
7864
|
-
{ title: "
|
|
8345
|
+
{ title: "\u2795 Add new instance", value: "add" },
|
|
7865
8346
|
{ title: "\uD83D\uDCCB List all instances", value: "list" },
|
|
7866
8347
|
...instances.length > 0 ? [
|
|
7867
|
-
{ title: "
|
|
7868
|
-
{ title: "\uD83D\
|
|
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" }
|
|
7869
8352
|
] : [],
|
|
7870
8353
|
{ title: "\uD83D\uDEAA Exit", value: "exit" }
|
|
7871
8354
|
],
|
|
@@ -7886,6 +8369,12 @@ async function runInteractiveMode() {
|
|
|
7886
8369
|
case "info":
|
|
7887
8370
|
await handleShowInstanceInfo(instances);
|
|
7888
8371
|
break;
|
|
8372
|
+
case "plugins":
|
|
8373
|
+
await handleManagePlugins(instances);
|
|
8374
|
+
break;
|
|
8375
|
+
case "autosync":
|
|
8376
|
+
await handleToggleAutoSync(instances);
|
|
8377
|
+
break;
|
|
7889
8378
|
case "remove":
|
|
7890
8379
|
await handleRemoveInstance(instances);
|
|
7891
8380
|
break;
|
|
@@ -7917,7 +8406,7 @@ async function runInteractiveMode() {
|
|
|
7917
8406
|
}
|
|
7918
8407
|
async function handleAddInstance() {
|
|
7919
8408
|
console.log(source_default.bold(`
|
|
7920
|
-
|
|
8409
|
+
\u2795 Add New Instance
|
|
7921
8410
|
`));
|
|
7922
8411
|
const { name } = await import_prompts.default({
|
|
7923
8412
|
type: "text",
|
|
@@ -7936,9 +8425,52 @@ async function handleAddInstance() {
|
|
|
7936
8425
|
return;
|
|
7937
8426
|
const existing = await getInstance(name);
|
|
7938
8427
|
if (existing) {
|
|
7939
|
-
console.log(source_default.red(
|
|
8428
|
+
console.log(source_default.red(`\u2717 Instance '${name}' already exists`));
|
|
7940
8429
|
return;
|
|
7941
8430
|
}
|
|
8431
|
+
const { useProvider } = await import_prompts.default({
|
|
8432
|
+
type: "confirm",
|
|
8433
|
+
name: "useProvider",
|
|
8434
|
+
message: "Would you like to use a provider template (GLM, MiniMax)?",
|
|
8435
|
+
initial: false
|
|
8436
|
+
});
|
|
8437
|
+
let providerTemplate = null;
|
|
8438
|
+
let apiKey = "";
|
|
8439
|
+
if (useProvider) {
|
|
8440
|
+
const providers = getAvailableProviders();
|
|
8441
|
+
const providerChoices = [
|
|
8442
|
+
...providers.map((p) => ({
|
|
8443
|
+
title: `${p.displayName} - ${p.description}`,
|
|
8444
|
+
value: p.name
|
|
8445
|
+
})),
|
|
8446
|
+
{ title: "None / Custom", value: "none" }
|
|
8447
|
+
];
|
|
8448
|
+
const { selectedProvider } = await import_prompts.default({
|
|
8449
|
+
type: "select",
|
|
8450
|
+
name: "selectedProvider",
|
|
8451
|
+
message: "Select a provider:",
|
|
8452
|
+
choices: providerChoices,
|
|
8453
|
+
initial: 0
|
|
8454
|
+
});
|
|
8455
|
+
if (!selectedProvider || selectedProvider === "none") {} else {
|
|
8456
|
+
providerTemplate = getProviderTemplate(selectedProvider);
|
|
8457
|
+
if (providerTemplate) {
|
|
8458
|
+
const { inputApiKey } = await import_prompts.default({
|
|
8459
|
+
type: "password",
|
|
8460
|
+
name: "inputApiKey",
|
|
8461
|
+
message: `Enter your ${providerTemplate.displayName} API key:`,
|
|
8462
|
+
validate: (value) => {
|
|
8463
|
+
if (!value.trim())
|
|
8464
|
+
return "API key is required";
|
|
8465
|
+
return true;
|
|
8466
|
+
}
|
|
8467
|
+
});
|
|
8468
|
+
if (!inputApiKey)
|
|
8469
|
+
return;
|
|
8470
|
+
apiKey = inputApiKey;
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
7942
8474
|
const { useDefaults } = await import_prompts.default({
|
|
7943
8475
|
type: "confirm",
|
|
7944
8476
|
name: "useDefaults",
|
|
@@ -7948,7 +8480,7 @@ async function handleAddInstance() {
|
|
|
7948
8480
|
let configDir;
|
|
7949
8481
|
let binaryPath;
|
|
7950
8482
|
if (useDefaults) {
|
|
7951
|
-
configDir =
|
|
8483
|
+
configDir = join3(homedir2(), `.claude-${name}`);
|
|
7952
8484
|
binaryPath = getDefaultBinaryPath(name);
|
|
7953
8485
|
} else {
|
|
7954
8486
|
const configResponse = await import_prompts.default([
|
|
@@ -7956,7 +8488,7 @@ async function handleAddInstance() {
|
|
|
7956
8488
|
type: "text",
|
|
7957
8489
|
name: "configDir",
|
|
7958
8490
|
message: "Config directory path:",
|
|
7959
|
-
initial:
|
|
8491
|
+
initial: join3(homedir2(), `.claude-${name}`)
|
|
7960
8492
|
},
|
|
7961
8493
|
{
|
|
7962
8494
|
type: "text",
|
|
@@ -8005,39 +8537,63 @@ async function handleAddInstance() {
|
|
|
8005
8537
|
copyMcpServers = copyOption === "mcp" || copyOption === "settings+mcp" || copyOption === "all";
|
|
8006
8538
|
copyAllFiles = copyOption === "all";
|
|
8007
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
|
+
}
|
|
8008
8553
|
const instance = {
|
|
8009
8554
|
name,
|
|
8010
8555
|
configDir,
|
|
8011
8556
|
binaryPath,
|
|
8012
|
-
createdAt: new Date().toISOString()
|
|
8557
|
+
createdAt: new Date().toISOString(),
|
|
8558
|
+
autoSync
|
|
8013
8559
|
};
|
|
8014
8560
|
await addInstance(instance);
|
|
8015
8561
|
await createWrapper(instance);
|
|
8016
8562
|
if (copySettings && !copyAllFiles) {
|
|
8017
8563
|
await copySettingsFromDefault(configDir);
|
|
8018
|
-
console.log(source_default.green("
|
|
8564
|
+
console.log(source_default.green("\u2713 Copied settings.json"));
|
|
8019
8565
|
}
|
|
8020
8566
|
if (copyMcpServers && !copyAllFiles) {
|
|
8021
8567
|
try {
|
|
8022
8568
|
await copyMcpServersFromDefault(configDir);
|
|
8023
|
-
console.log(source_default.green("
|
|
8569
|
+
console.log(source_default.green("\u2713 Copied MCP server configurations"));
|
|
8024
8570
|
} catch (error) {
|
|
8025
|
-
console.log(source_default.yellow(
|
|
8571
|
+
console.log(source_default.yellow(`\u26A0 Warning: ${error.message}`));
|
|
8026
8572
|
}
|
|
8027
8573
|
}
|
|
8028
8574
|
if (copyAllFiles) {
|
|
8029
|
-
await copyAllFromDefault(configDir);
|
|
8030
|
-
|
|
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
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
if (providerTemplate && !copySettings && !copyAllFiles) {
|
|
8583
|
+
await createSettingsFromTemplate(configDir, providerTemplate, apiKey);
|
|
8584
|
+
console.log(source_default.green(`\u2713 Applied ${providerTemplate.displayName} template`));
|
|
8585
|
+
} else if (providerTemplate && (copySettings || copyAllFiles)) {
|
|
8586
|
+
console.log(source_default.yellow("\u26A0 Provider template skipped (copied settings from default Claude)"));
|
|
8031
8587
|
}
|
|
8032
8588
|
console.log(source_default.green(`
|
|
8033
|
-
|
|
8589
|
+
\u2713 Instance '${name}' created successfully!`));
|
|
8034
8590
|
console.log(source_default.gray(` Binary: ${binaryPath}`));
|
|
8035
8591
|
console.log(source_default.gray(` Config: ${configDir}`));
|
|
8036
8592
|
const binDir = binaryPath.substring(0, binaryPath.lastIndexOf("/"));
|
|
8037
8593
|
const pathEnv = process.env.PATH || "";
|
|
8038
8594
|
const isInPath = pathEnv.split(":").some((p) => p === binDir);
|
|
8039
8595
|
if (!isInPath) {
|
|
8040
|
-
console.log(source_default.yellow(
|
|
8596
|
+
console.log(source_default.yellow(`\u26A0 Warning: ${binDir} is not in your PATH`));
|
|
8041
8597
|
console.log(source_default.gray(`Add to PATH by running:`));
|
|
8042
8598
|
console.log(source_default.cyan(` echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc`));
|
|
8043
8599
|
console.log(source_default.cyan(` source ~/.zshrc`));
|
|
@@ -8055,10 +8611,12 @@ async function handleListInstances(instances) {
|
|
|
8055
8611
|
return;
|
|
8056
8612
|
}
|
|
8057
8613
|
for (const instance of instances) {
|
|
8058
|
-
console.log(source_default.cyan(
|
|
8614
|
+
console.log(source_default.cyan(`\u25CF ${instance.name}`));
|
|
8059
8615
|
console.log(source_default.gray(` Binary: ${instance.binaryPath}`));
|
|
8060
8616
|
console.log(source_default.gray(` Config: ${instance.configDir}`));
|
|
8061
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}`));
|
|
8062
8620
|
console.log();
|
|
8063
8621
|
}
|
|
8064
8622
|
}
|
|
@@ -8068,7 +8626,7 @@ async function handleShowInstanceInfo(instances) {
|
|
|
8068
8626
|
return;
|
|
8069
8627
|
}
|
|
8070
8628
|
console.log(source_default.bold(`
|
|
8071
|
-
|
|
8629
|
+
\u2139\uFE0F Instance Details
|
|
8072
8630
|
`));
|
|
8073
8631
|
const { instanceName } = await import_prompts.default({
|
|
8074
8632
|
type: "select",
|
|
@@ -8089,6 +8647,8 @@ Instance: ${source_default.cyan(instance.name)}`));
|
|
|
8089
8647
|
console.log(`${source_default.gray("Binary:")} ${instance.binaryPath}`);
|
|
8090
8648
|
console.log(`${source_default.gray("Config:")} ${instance.configDir}`);
|
|
8091
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}`);
|
|
8092
8652
|
}
|
|
8093
8653
|
async function handleRemoveInstance(instances) {
|
|
8094
8654
|
if (instances.length === 0) {
|
|
@@ -8096,7 +8656,7 @@ async function handleRemoveInstance(instances) {
|
|
|
8096
8656
|
return;
|
|
8097
8657
|
}
|
|
8098
8658
|
console.log(source_default.bold(`
|
|
8099
|
-
\uD83D\uDDD1
|
|
8659
|
+
\uD83D\uDDD1\uFE0F Remove Instance
|
|
8100
8660
|
`));
|
|
8101
8661
|
const { instanceName } = await import_prompts.default({
|
|
8102
8662
|
type: "select",
|
|
@@ -8119,14 +8679,176 @@ async function handleRemoveInstance(instances) {
|
|
|
8119
8679
|
initial: false
|
|
8120
8680
|
});
|
|
8121
8681
|
if (!confirm) {
|
|
8122
|
-
console.log(source_default.yellow("
|
|
8682
|
+
console.log(source_default.yellow("\u2717 Cancelled"));
|
|
8123
8683
|
return;
|
|
8124
8684
|
}
|
|
8125
8685
|
await removeInstance(instanceName);
|
|
8126
8686
|
removeWrapper(instance.binaryPath);
|
|
8127
|
-
console.log(source_default.green(
|
|
8687
|
+
console.log(source_default.green(`\u2713 Instance '${instanceName}' removed successfully!`));
|
|
8128
8688
|
console.log(source_default.gray(`To remove config files, run: rm -rf ${instance.configDir}`));
|
|
8129
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
|
+
}
|
|
8130
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 = "") => {
|
|
8131
8853
|
try {
|
|
8132
8854
|
switch (action) {
|
|
@@ -8140,12 +8862,12 @@ program2.command("mcp").description("Manage MCP server configurations").argument
|
|
|
8140
8862
|
await handleMcpVerify(instance);
|
|
8141
8863
|
break;
|
|
8142
8864
|
default:
|
|
8143
|
-
console.error(source_default.red(
|
|
8865
|
+
console.error(source_default.red(`\u2717 Unknown action: ${action}`));
|
|
8144
8866
|
console.log(source_default.gray("Available actions: list, copy, verify"));
|
|
8145
8867
|
process.exit(1);
|
|
8146
8868
|
}
|
|
8147
8869
|
} catch (error) {
|
|
8148
|
-
console.error(source_default.red(
|
|
8870
|
+
console.error(source_default.red(`\u2717 Error: ${error.message}`));
|
|
8149
8871
|
process.exit(1);
|
|
8150
8872
|
}
|
|
8151
8873
|
});
|
|
@@ -8163,11 +8885,11 @@ async function handleMcpList(instanceName) {
|
|
|
8163
8885
|
for (const instance of instances) {
|
|
8164
8886
|
const mcpServers = await listMcpServers(instance.name);
|
|
8165
8887
|
const hasMcp = mcpServers && Object.keys(mcpServers).length > 0;
|
|
8166
|
-
console.log(source_default.cyan(
|
|
8888
|
+
console.log(source_default.cyan(`\u25CF ${instance.name}`));
|
|
8167
8889
|
console.log(source_default.gray(` MCP Servers: ${hasMcp ? Object.keys(mcpServers).length : "None"}`));
|
|
8168
8890
|
if (hasMcp) {
|
|
8169
8891
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
8170
|
-
console.log(source_default.gray(`
|
|
8892
|
+
console.log(source_default.gray(` \u2022 ${serverName} (${serverConfig.type})`));
|
|
8171
8893
|
}
|
|
8172
8894
|
}
|
|
8173
8895
|
console.log();
|
|
@@ -8182,7 +8904,7 @@ async function handleMcpList(instanceName) {
|
|
|
8182
8904
|
\uD83D\uDCCB MCP Servers in '${instanceName}'
|
|
8183
8905
|
`));
|
|
8184
8906
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
8185
|
-
console.log(source_default.cyan(
|
|
8907
|
+
console.log(source_default.cyan(`\u25CF ${serverName}`));
|
|
8186
8908
|
console.log(source_default.gray(` Type: ${serverConfig.type}`));
|
|
8187
8909
|
if (serverConfig.command) {
|
|
8188
8910
|
console.log(source_default.gray(` Command: ${serverConfig.command}`));
|
|
@@ -8245,7 +8967,7 @@ async function handleMcpCopy(sourceInstanceName, targetInstanceName) {
|
|
|
8245
8967
|
\uD83D\uDD04 Copying MCP servers from '${source}' to '${target}'
|
|
8246
8968
|
`));
|
|
8247
8969
|
await copyMcpServersBetweenInstances(source, target);
|
|
8248
|
-
console.log(source_default.green(
|
|
8970
|
+
console.log(source_default.green(`\u2713 MCP servers copied successfully!`));
|
|
8249
8971
|
}
|
|
8250
8972
|
async function handleMcpVerify(instanceName) {
|
|
8251
8973
|
const instances = await listInstances();
|
|
@@ -8273,20 +8995,48 @@ async function handleMcpVerify(instanceName) {
|
|
|
8273
8995
|
`));
|
|
8274
8996
|
const mcpServers = await listMcpServers(instance);
|
|
8275
8997
|
if (!mcpServers) {
|
|
8276
|
-
console.log(source_default.yellow("
|
|
8998
|
+
console.log(source_default.yellow("\u26A0 No MCP configuration found"));
|
|
8277
8999
|
return;
|
|
8278
9000
|
}
|
|
8279
9001
|
const serverCount = Object.keys(mcpServers).length;
|
|
8280
|
-
console.log(source_default.green(
|
|
9002
|
+
console.log(source_default.green(`\u2713 Found ${serverCount} MCP server(s)`));
|
|
8281
9003
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
8282
|
-
console.log(source_default.gray(`
|
|
9004
|
+
console.log(source_default.gray(` \u2022 ${serverName}: ${serverConfig.type}`));
|
|
8283
9005
|
if (serverConfig.type === "stdio" && !serverConfig.command) {
|
|
8284
|
-
console.log(source_default.yellow(`
|
|
9006
|
+
console.log(source_default.yellow(` \u26A0 Missing command for stdio server`));
|
|
8285
9007
|
} else if ((serverConfig.type === "http" || serverConfig.type === "sse") && !serverConfig.url) {
|
|
8286
|
-
console.log(source_default.yellow(`
|
|
9008
|
+
console.log(source_default.yellow(` \u26A0 Missing URL for ${serverConfig.type} server`));
|
|
8287
9009
|
} else {
|
|
8288
|
-
console.log(source_default.green(`
|
|
9010
|
+
console.log(source_default.green(` \u2713 Configuration looks valid`));
|
|
8289
9011
|
}
|
|
8290
9012
|
}
|
|
8291
9013
|
}
|
|
8292
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(() => {});
|