@cbuk100011/claude-mode 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -10
- package/dist/index.js +682 -67
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Claude Mode
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
A Node.js CLI launcher for Claude Code that supports multiple AI providers and models.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
@@ -17,12 +19,19 @@ A Node.js CLI launcher for Claude Code that supports multiple AI providers and m
|
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
22
|
+
### npm Package (Recommended)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Install globally
|
|
26
|
+
npm install -g @cbuk100011/claude-mode
|
|
27
|
+
```
|
|
28
|
+
|
|
20
29
|
### From Source
|
|
21
30
|
|
|
22
31
|
```bash
|
|
23
32
|
# Clone the repository
|
|
24
|
-
git clone
|
|
25
|
-
cd claude-mode
|
|
33
|
+
git clone https://github.com/Cam10001110101/claude-mode-cli.git
|
|
34
|
+
cd claude-mode-cli
|
|
26
35
|
|
|
27
36
|
# Install dependencies
|
|
28
37
|
npm install
|
|
@@ -30,16 +39,10 @@ npm install
|
|
|
30
39
|
# Build
|
|
31
40
|
npm run build
|
|
32
41
|
|
|
33
|
-
# Link globally
|
|
42
|
+
# Link globally
|
|
34
43
|
npm link
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
### Global Installation (after npm publish)
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npm install -g claude-mode
|
|
41
|
-
```
|
|
42
|
-
|
|
43
46
|
## Usage
|
|
44
47
|
|
|
45
48
|
### Interactive Menu
|
|
@@ -293,7 +296,7 @@ npm run typecheck
|
|
|
293
296
|
## Project Structure
|
|
294
297
|
|
|
295
298
|
```
|
|
296
|
-
claude-mode/
|
|
299
|
+
claude-mode-cli/
|
|
297
300
|
├── src/
|
|
298
301
|
│ ├── index.ts # Main CLI entry point
|
|
299
302
|
│ ├── providers.ts # Provider and model configurations
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
6
|
-
import { select, input } from "@inquirer/prompts";
|
|
5
|
+
import chalk3 from "chalk";
|
|
6
|
+
import { select as select2, input as input2, confirm as confirm2 } from "@inquirer/prompts";
|
|
7
7
|
import { spawn, execSync } from "child_process";
|
|
8
8
|
import { config } from "dotenv";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
|
-
import
|
|
10
|
+
import path3 from "path";
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -37,6 +37,7 @@ var DEFAULT_CONFIG = {
|
|
|
37
37
|
healthCheckTimeout: 2e3,
|
|
38
38
|
cacheTTL: 3e4,
|
|
39
39
|
customProviders: [],
|
|
40
|
+
configuredProviders: [],
|
|
40
41
|
skipHealthCheck: false,
|
|
41
42
|
offlineMode: false,
|
|
42
43
|
headlessAllowedTools: "Read,Edit,Write,Bash,Glob,Grep"
|
|
@@ -210,6 +211,33 @@ function claudeNotFoundError() {
|
|
|
210
211
|
hint: "Install Claude Code CLI: npm install -g @anthropic-ai/claude-code\nOr check that it is in your PATH."
|
|
211
212
|
};
|
|
212
213
|
}
|
|
214
|
+
function setupConfigWriteError(message, hint) {
|
|
215
|
+
return {
|
|
216
|
+
code: "SETUP_CONFIG_WRITE_FAILED" /* SETUP_CONFIG_WRITE_FAILED */,
|
|
217
|
+
message: `Failed to write config: ${message}`,
|
|
218
|
+
hint: hint || "Check file permissions and try again, or use a different config location."
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function setupEnvWriteError(message, hint) {
|
|
222
|
+
return {
|
|
223
|
+
code: "SETUP_ENV_WRITE_FAILED" /* SETUP_ENV_WRITE_FAILED */,
|
|
224
|
+
message: `Failed to write .env file: ${message}`,
|
|
225
|
+
hint: hint || "Check file permissions or specify a different location."
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function setupCancelledError() {
|
|
229
|
+
return {
|
|
230
|
+
code: "SETUP_CANCELLED" /* SETUP_CANCELLED */,
|
|
231
|
+
message: "Setup cancelled"
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function setupIncompleteError(message) {
|
|
235
|
+
return {
|
|
236
|
+
code: "SETUP_INCOMPLETE" /* SETUP_INCOMPLETE */,
|
|
237
|
+
message,
|
|
238
|
+
hint: "Run claude-mode setup to complete configuration."
|
|
239
|
+
};
|
|
240
|
+
}
|
|
213
241
|
|
|
214
242
|
// src/providers.ts
|
|
215
243
|
var builtInProviders = {
|
|
@@ -249,10 +277,10 @@ var builtInProviders = {
|
|
|
249
277
|
"ollama-custom": {
|
|
250
278
|
name: "Ollama Custom",
|
|
251
279
|
key: "ollama-custom",
|
|
252
|
-
getBaseUrl: () => process.env.OLLAMA_BASE_URL_CUSTOM || "
|
|
280
|
+
getBaseUrl: () => process.env.OLLAMA_BASE_URL_CUSTOM || "",
|
|
253
281
|
getAuthToken: () => "ollama",
|
|
254
282
|
getDescription: () => {
|
|
255
|
-
const url = process.env.OLLAMA_BASE_URL_CUSTOM || "
|
|
283
|
+
const url = process.env.OLLAMA_BASE_URL_CUSTOM || "not configured";
|
|
256
284
|
return `Ollama Custom (${url})`;
|
|
257
285
|
},
|
|
258
286
|
isBuiltIn: true
|
|
@@ -508,12 +536,57 @@ async function checkAllProvidersHealth() {
|
|
|
508
536
|
return results;
|
|
509
537
|
}
|
|
510
538
|
|
|
511
|
-
// src/
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
539
|
+
// src/setup.ts
|
|
540
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
541
|
+
import { homedir as homedir2 } from "os";
|
|
542
|
+
import path2 from "path";
|
|
543
|
+
import chalk2 from "chalk";
|
|
544
|
+
import {
|
|
545
|
+
confirm,
|
|
546
|
+
select,
|
|
547
|
+
checkbox,
|
|
548
|
+
input,
|
|
549
|
+
password
|
|
550
|
+
} from "@inquirer/prompts";
|
|
551
|
+
function getConfigDir2() {
|
|
552
|
+
return path2.join(homedir2(), ".claude-mode");
|
|
553
|
+
}
|
|
554
|
+
function getConfigPath() {
|
|
555
|
+
return path2.join(getConfigDir2(), "claude-mode.json");
|
|
556
|
+
}
|
|
557
|
+
function getEnvPath(envPath) {
|
|
558
|
+
return envPath || path2.join(process.cwd(), ".env");
|
|
559
|
+
}
|
|
560
|
+
function detectSetupStatus() {
|
|
561
|
+
const configPath = getConfigPath();
|
|
562
|
+
const envPath = getEnvPath();
|
|
563
|
+
if (!existsSync2(configPath)) {
|
|
564
|
+
return "first-time";
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
const config2 = loadConfig();
|
|
568
|
+
const hasDefaults = !!(config2.defaultProvider && config2.defaultModel);
|
|
569
|
+
const hasProviders = !!config2.configuredProviders && config2.configuredProviders.length > 0;
|
|
570
|
+
if (!hasDefaults || !hasProviders) {
|
|
571
|
+
return "incomplete";
|
|
572
|
+
}
|
|
573
|
+
if (config2.configuredProviders) {
|
|
574
|
+
for (const providerKey of config2.configuredProviders) {
|
|
575
|
+
const provider = getProvider(providerKey);
|
|
576
|
+
if (provider) {
|
|
577
|
+
const hasKey = !!provider.getAuthToken();
|
|
578
|
+
const needsKey = providerKey === "openrouter" || providerKey === "ollama-cloud";
|
|
579
|
+
if (needsKey && !hasKey) {
|
|
580
|
+
return "incomplete";
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return "complete";
|
|
586
|
+
} catch {
|
|
587
|
+
return "first-time";
|
|
588
|
+
}
|
|
589
|
+
}
|
|
517
590
|
function printHeader(text) {
|
|
518
591
|
console.log("");
|
|
519
592
|
console.log(chalk2.bold.blue("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
@@ -521,13 +594,538 @@ function printHeader(text) {
|
|
|
521
594
|
console.log(chalk2.bold.blue("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
522
595
|
console.log("");
|
|
523
596
|
}
|
|
597
|
+
function printSection(title) {
|
|
598
|
+
console.log("");
|
|
599
|
+
console.log(chalk2.bold.cyan("\u2501\u2501\u2501 " + title + " \u2501\u2501\u2501"));
|
|
600
|
+
console.log("");
|
|
601
|
+
}
|
|
602
|
+
function printProviderInfo() {
|
|
603
|
+
console.log(chalk2.bold("Available Providers:"));
|
|
604
|
+
console.log("");
|
|
605
|
+
console.log(" " + chalk2.green("OpenRouter") + " - Access to Claude, GPT-5, DeepSeek, and more");
|
|
606
|
+
console.log(" " + chalk2.gray("Requires: API key"));
|
|
607
|
+
console.log("");
|
|
608
|
+
console.log(" " + chalk2.green("Ollama Local") + " - Run models locally on your machine");
|
|
609
|
+
console.log(" " + chalk2.gray("Requires: Local Ollama instance"));
|
|
610
|
+
console.log("");
|
|
611
|
+
console.log(" " + chalk2.green("Ollama Cloud") + " - Managed Ollama service");
|
|
612
|
+
console.log(" " + chalk2.gray("Requires: API key"));
|
|
613
|
+
console.log("");
|
|
614
|
+
console.log(" " + chalk2.green("Ollama Custom") + " - Connect to remote Ollama instance");
|
|
615
|
+
console.log(" " + chalk2.gray("Requires: Custom URL"));
|
|
616
|
+
console.log("");
|
|
617
|
+
}
|
|
618
|
+
async function displayWelcome(isFirstTime) {
|
|
619
|
+
console.clear();
|
|
620
|
+
if (isFirstTime) {
|
|
621
|
+
printHeader("Welcome to Claude Mode Setup");
|
|
622
|
+
console.log(chalk2.bold.cyan("Claude Mode") + " is a CLI launcher for Claude Code");
|
|
623
|
+
console.log("that supports multiple AI providers.");
|
|
624
|
+
console.log("");
|
|
625
|
+
console.log("This wizard will help you configure your providers");
|
|
626
|
+
console.log("and set up your default settings.");
|
|
627
|
+
console.log("");
|
|
628
|
+
} else {
|
|
629
|
+
printHeader("Claude Mode Configuration");
|
|
630
|
+
console.log("Update your configuration or add new providers.");
|
|
631
|
+
console.log("");
|
|
632
|
+
}
|
|
633
|
+
printProviderInfo();
|
|
634
|
+
const ready = await confirm({
|
|
635
|
+
message: "Ready to configure?",
|
|
636
|
+
default: true
|
|
637
|
+
});
|
|
638
|
+
if (!ready) {
|
|
639
|
+
throw setupCancelledError();
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async function displayUpdatePrompt() {
|
|
643
|
+
console.clear();
|
|
644
|
+
printHeader("Configuration Found");
|
|
645
|
+
console.log("A configuration file already exists.");
|
|
646
|
+
console.log("");
|
|
647
|
+
console.log("You can:");
|
|
648
|
+
console.log(" " + chalk2.cyan("\u2022") + " Re-run full setup to reconfigure all providers");
|
|
649
|
+
console.log(" " + chalk2.cyan("\u2022") + " Configure a single provider");
|
|
650
|
+
console.log(" " + chalk2.cyan("\u2022") + " Skip and use existing configuration");
|
|
651
|
+
console.log("");
|
|
652
|
+
const action = await select({
|
|
653
|
+
message: "What would you like to do?",
|
|
654
|
+
choices: [
|
|
655
|
+
{ name: "Re-run full setup", value: "full" },
|
|
656
|
+
{ name: "Configure specific provider", value: "single" },
|
|
657
|
+
{ name: "Skip (use existing config)", value: "skip" }
|
|
658
|
+
]
|
|
659
|
+
});
|
|
660
|
+
return action !== "skip";
|
|
661
|
+
}
|
|
662
|
+
async function selectProviders(forceProvider) {
|
|
663
|
+
if (forceProvider) {
|
|
664
|
+
const provider = getProvider(forceProvider);
|
|
665
|
+
if (!provider) {
|
|
666
|
+
console.error(chalk2.red(`Unknown provider: ${forceProvider}`));
|
|
667
|
+
throw setupIncompleteError(`Provider "${forceProvider}" not found`);
|
|
668
|
+
}
|
|
669
|
+
return [forceProvider];
|
|
670
|
+
}
|
|
671
|
+
const allProviders = getProviders();
|
|
672
|
+
const choices = Object.entries(allProviders).map(([key, provider]) => ({
|
|
673
|
+
name: provider.name,
|
|
674
|
+
value: key,
|
|
675
|
+
description: provider.getDescription()
|
|
676
|
+
}));
|
|
677
|
+
const selected = await checkbox({
|
|
678
|
+
message: "Select providers to configure:",
|
|
679
|
+
choices,
|
|
680
|
+
validate: (value) => value.length > 0 ? true : "Select at least one provider"
|
|
681
|
+
});
|
|
682
|
+
return selected;
|
|
683
|
+
}
|
|
684
|
+
async function configureProvider(providerKey, skipValidation) {
|
|
685
|
+
const provider = getProvider(providerKey);
|
|
686
|
+
if (!provider) {
|
|
687
|
+
throw setupIncompleteError(`Provider "${providerKey}" not found`);
|
|
688
|
+
}
|
|
689
|
+
printSection(`Configuring ${provider.name}`);
|
|
690
|
+
const config2 = {
|
|
691
|
+
key: providerKey,
|
|
692
|
+
name: provider.name,
|
|
693
|
+
validated: false
|
|
694
|
+
};
|
|
695
|
+
if (providerKey === "openrouter" || providerKey === "ollama-cloud") {
|
|
696
|
+
const existingKey = provider.getAuthToken();
|
|
697
|
+
const keyMsg = existingKey ? `Enter API key (current: ${existingKey.substring(0, 8)}...)` : "Enter API key:";
|
|
698
|
+
config2.apiKey = await password({
|
|
699
|
+
message: keyMsg,
|
|
700
|
+
validate: (value) => {
|
|
701
|
+
if (existingKey && !value) return true;
|
|
702
|
+
return value.length > 0 ? true : "API key is required";
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
if (config2.apiKey) {
|
|
706
|
+
if (providerKey === "openrouter") {
|
|
707
|
+
process.env.ANTHROPIC_AUTH_TOKEN = config2.apiKey;
|
|
708
|
+
} else if (providerKey === "ollama-cloud") {
|
|
709
|
+
process.env.OLLAMA_API_KEY = config2.apiKey;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (providerKey === "ollama-custom") {
|
|
714
|
+
const existingUrl = provider.getBaseUrl();
|
|
715
|
+
config2.customUrl = await input({
|
|
716
|
+
message: "Enter Ollama URL:",
|
|
717
|
+
default: existingUrl || "",
|
|
718
|
+
validate: (value) => {
|
|
719
|
+
try {
|
|
720
|
+
new URL(value);
|
|
721
|
+
return true;
|
|
722
|
+
} catch {
|
|
723
|
+
return "Please enter a valid URL";
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
if (config2.customUrl) {
|
|
728
|
+
process.env.OLLAMA_BASE_URL_CUSTOM = config2.customUrl;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (!skipValidation) {
|
|
732
|
+
config2.validated = await validateProvider(providerKey);
|
|
733
|
+
} else {
|
|
734
|
+
config2.validated = true;
|
|
735
|
+
}
|
|
736
|
+
return config2;
|
|
737
|
+
}
|
|
738
|
+
async function validateProvider(providerKey) {
|
|
739
|
+
console.log(chalk2.gray("Validating provider connection..."));
|
|
740
|
+
const result = await checkProviderHealth(providerKey);
|
|
741
|
+
if (result.healthy) {
|
|
742
|
+
const latency = result.latencyMs ? ` (${result.latencyMs}ms)` : "";
|
|
743
|
+
console.log(chalk2.green(`\u2713 ${providerKey} is reachable${latency}`));
|
|
744
|
+
return true;
|
|
745
|
+
} else {
|
|
746
|
+
console.log(chalk2.yellow(`\u26A0 ${providerKey} validation failed`));
|
|
747
|
+
if (result.error) {
|
|
748
|
+
console.log(chalk2.gray(` ${result.error.message}`));
|
|
749
|
+
}
|
|
750
|
+
const retry = await confirm({
|
|
751
|
+
message: "Continue anyway?",
|
|
752
|
+
default: false
|
|
753
|
+
});
|
|
754
|
+
return retry;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async function selectDefaults(configuredProviders, currentDefaults) {
|
|
758
|
+
printSection("Set Defaults");
|
|
759
|
+
let defaultProvider;
|
|
760
|
+
let defaultModel;
|
|
761
|
+
if (currentDefaults && configuredProviders.includes(currentDefaults.provider)) {
|
|
762
|
+
const useCurrent = await confirm({
|
|
763
|
+
message: `Keep default provider as ${currentDefaults.provider}?`,
|
|
764
|
+
default: true
|
|
765
|
+
});
|
|
766
|
+
if (useCurrent) {
|
|
767
|
+
defaultProvider = currentDefaults.provider;
|
|
768
|
+
} else {
|
|
769
|
+
defaultProvider = await select({
|
|
770
|
+
message: "Select default provider:",
|
|
771
|
+
choices: configuredProviders.map((key) => ({
|
|
772
|
+
name: getProvider(key)?.name || key,
|
|
773
|
+
value: key
|
|
774
|
+
}))
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
defaultProvider = await select({
|
|
779
|
+
message: "Select default provider:",
|
|
780
|
+
choices: configuredProviders.map((key) => ({
|
|
781
|
+
name: getProvider(key)?.name || key,
|
|
782
|
+
value: key
|
|
783
|
+
}))
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
const models3 = await getModels(defaultProvider);
|
|
787
|
+
console.log("");
|
|
788
|
+
console.log(chalk2.gray("Available models for " + getProvider(defaultProvider)?.name + ":"));
|
|
789
|
+
if (models3.length === 0) {
|
|
790
|
+
console.log(" " + chalk2.gray("(no models found - will use manual input)"));
|
|
791
|
+
console.log("");
|
|
792
|
+
const modelInput = await input({
|
|
793
|
+
message: "Enter default model ID:",
|
|
794
|
+
validate: (value) => value.length > 0 ? true : "Model ID cannot be empty"
|
|
795
|
+
});
|
|
796
|
+
defaultModel = modelInput;
|
|
797
|
+
} else {
|
|
798
|
+
for (const model of models3) {
|
|
799
|
+
console.log(` ${chalk2.green(model.shortcut.padEnd(20))} ${chalk2.gray(model.id)}`);
|
|
800
|
+
}
|
|
801
|
+
console.log("");
|
|
802
|
+
defaultModel = await select({
|
|
803
|
+
message: "Select default model:",
|
|
804
|
+
choices: models3.map((model) => ({
|
|
805
|
+
name: `${model.name} (${model.shortcut})`,
|
|
806
|
+
value: model.id
|
|
807
|
+
}))
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
return { provider: defaultProvider, model: defaultModel };
|
|
811
|
+
}
|
|
812
|
+
function formatProviderConfig(config2) {
|
|
813
|
+
const lines = [];
|
|
814
|
+
lines.push(chalk2.cyan(` ${config2.name} (${config2.key})`));
|
|
815
|
+
if (config2.apiKey) {
|
|
816
|
+
const masked = config2.apiKey.substring(0, 8) + "..." + config2.apiKey.slice(-4);
|
|
817
|
+
lines.push(` API Key: ${chalk2.gray(masked)}`);
|
|
818
|
+
}
|
|
819
|
+
if (config2.customUrl) {
|
|
820
|
+
lines.push(` URL: ${chalk2.gray(config2.customUrl)}`);
|
|
821
|
+
}
|
|
822
|
+
if (config2.validated) {
|
|
823
|
+
lines.push(` Status: ${chalk2.green("\u2713 Validated")}`);
|
|
824
|
+
} else {
|
|
825
|
+
lines.push(` Status: ${chalk2.yellow("\u26A0 Not validated")}`);
|
|
826
|
+
}
|
|
827
|
+
return lines.join("\n");
|
|
828
|
+
}
|
|
829
|
+
async function reviewAndConfirm(providerConfigs, defaults, currentConfig) {
|
|
830
|
+
printSection("Review Configuration");
|
|
831
|
+
console.log(chalk2.bold("Configured Providers:"));
|
|
832
|
+
console.log("");
|
|
833
|
+
for (const config2 of providerConfigs) {
|
|
834
|
+
console.log(formatProviderConfig(config2));
|
|
835
|
+
}
|
|
836
|
+
console.log("");
|
|
837
|
+
console.log(chalk2.bold("Defaults:"));
|
|
838
|
+
console.log(` ${chalk2.cyan("Provider:")} ${defaults.provider}`);
|
|
839
|
+
console.log(` ${chalk2.cyan("Model:")} ${defaults.model}`);
|
|
840
|
+
console.log("");
|
|
841
|
+
console.log(chalk2.bold("Files to be created/updated:"));
|
|
842
|
+
console.log(` ${chalk2.cyan("Config:")} ${getConfigPath()}`);
|
|
843
|
+
console.log(` ${chalk2.cyan("Env:")} ${getEnvPath()}`);
|
|
844
|
+
console.log("");
|
|
845
|
+
const confirmed = await confirm({
|
|
846
|
+
message: "Save this configuration?",
|
|
847
|
+
default: true
|
|
848
|
+
});
|
|
849
|
+
return confirmed;
|
|
850
|
+
}
|
|
851
|
+
function buildEnvVars(providerConfigs) {
|
|
852
|
+
const envVars = {};
|
|
853
|
+
for (const config2 of providerConfigs) {
|
|
854
|
+
switch (config2.key) {
|
|
855
|
+
case "openrouter":
|
|
856
|
+
if (config2.apiKey) {
|
|
857
|
+
envVars.ANTHROPIC_BASE_URL = "https://openrouter.ai/api";
|
|
858
|
+
envVars.ANTHROPIC_AUTH_TOKEN = config2.apiKey;
|
|
859
|
+
envVars.OPEN_ROUTER_API_KEY = config2.apiKey;
|
|
860
|
+
}
|
|
861
|
+
break;
|
|
862
|
+
case "ollama-cloud":
|
|
863
|
+
if (config2.apiKey) {
|
|
864
|
+
envVars.OLLAMA_HOST = "https://ollama.com";
|
|
865
|
+
envVars.OLLAMA_API_KEY = config2.apiKey;
|
|
866
|
+
}
|
|
867
|
+
break;
|
|
868
|
+
case "ollama-local":
|
|
869
|
+
envVars.OLLAMA_BASE_URL_LOCAL = "http://localhost:11434";
|
|
870
|
+
break;
|
|
871
|
+
case "ollama-custom":
|
|
872
|
+
if (config2.customUrl && config2.customUrl.trim()) {
|
|
873
|
+
envVars.OLLAMA_BASE_URL_CUSTOM = config2.customUrl;
|
|
874
|
+
}
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return envVars;
|
|
879
|
+
}
|
|
880
|
+
function formatEnvFile(envVars) {
|
|
881
|
+
const lines = [];
|
|
882
|
+
lines.push("# claude-mode configuration");
|
|
883
|
+
lines.push("# Generated by: claude-mode setup");
|
|
884
|
+
lines.push(`# Date: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
885
|
+
lines.push("");
|
|
886
|
+
if (envVars.ANTHROPIC_BASE_URL || envVars.ANTHROPIC_AUTH_TOKEN) {
|
|
887
|
+
lines.push("# OpenRouter");
|
|
888
|
+
if (envVars.ANTHROPIC_BASE_URL) {
|
|
889
|
+
lines.push(`ANTHROPIC_BASE_URL=${envVars.ANTHROPIC_BASE_URL}`);
|
|
890
|
+
}
|
|
891
|
+
if (envVars.ANTHROPIC_AUTH_TOKEN) {
|
|
892
|
+
lines.push(`ANTHROPIC_AUTH_TOKEN=${envVars.ANTHROPIC_AUTH_TOKEN}`);
|
|
893
|
+
}
|
|
894
|
+
if (envVars.OPEN_ROUTER_API_KEY) {
|
|
895
|
+
lines.push(`OPEN_ROUTER_API_KEY=${envVars.OPEN_ROUTER_API_KEY}`);
|
|
896
|
+
}
|
|
897
|
+
lines.push("");
|
|
898
|
+
}
|
|
899
|
+
if (envVars.OLLAMA_HOST || envVars.OLLAMA_API_KEY) {
|
|
900
|
+
lines.push("# Ollama Cloud");
|
|
901
|
+
if (envVars.OLLAMA_HOST) {
|
|
902
|
+
lines.push(`OLLAMA_HOST=${envVars.OLLAMA_HOST}`);
|
|
903
|
+
}
|
|
904
|
+
if (envVars.OLLAMA_API_KEY) {
|
|
905
|
+
lines.push(`OLLAMA_API_KEY=${envVars.OLLAMA_API_KEY}`);
|
|
906
|
+
}
|
|
907
|
+
lines.push("");
|
|
908
|
+
}
|
|
909
|
+
if (envVars.OLLAMA_BASE_URL_LOCAL) {
|
|
910
|
+
lines.push("# Ollama Local");
|
|
911
|
+
lines.push(`OLLAMA_BASE_URL_LOCAL=${envVars.OLLAMA_BASE_URL_LOCAL}`);
|
|
912
|
+
lines.push("");
|
|
913
|
+
}
|
|
914
|
+
if (envVars.OLLAMA_BASE_URL_CUSTOM) {
|
|
915
|
+
lines.push("# Ollama Custom");
|
|
916
|
+
lines.push(`OLLAMA_BASE_URL_CUSTOM=${envVars.OLLAMA_BASE_URL_CUSTOM}`);
|
|
917
|
+
lines.push("");
|
|
918
|
+
}
|
|
919
|
+
return lines.join("\n");
|
|
920
|
+
}
|
|
921
|
+
function mergeEnvFile(envVars, envPath) {
|
|
922
|
+
const targetPath = getEnvPath(envPath);
|
|
923
|
+
const existingVars = loadEnvFile(targetPath);
|
|
924
|
+
const merged = { ...existingVars, ...envVars };
|
|
925
|
+
const lines = [];
|
|
926
|
+
lines.push("# claude-mode configuration");
|
|
927
|
+
lines.push("# Generated by: claude-mode setup");
|
|
928
|
+
lines.push(`# Date: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
929
|
+
lines.push("");
|
|
930
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
931
|
+
if (!key.startsWith("_claude_mode_")) {
|
|
932
|
+
lines.push(`${key}=${value}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
try {
|
|
936
|
+
writeFileSync2(targetPath, lines.join("\n") + "\n", "utf-8");
|
|
937
|
+
} catch (error) {
|
|
938
|
+
const classified = classifyError(error);
|
|
939
|
+
throw setupEnvWriteError(classified.message, classified.hint);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function saveEnvFile(envVars, envPath) {
|
|
943
|
+
const targetPath = getEnvPath(envPath);
|
|
944
|
+
try {
|
|
945
|
+
const content = formatEnvFile(envVars);
|
|
946
|
+
writeFileSync2(targetPath, content + "\n", "utf-8");
|
|
947
|
+
} catch (error) {
|
|
948
|
+
const classified = classifyError(error);
|
|
949
|
+
throw setupEnvWriteError(classified.message, classified.hint);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
function loadEnvFile(envPath) {
|
|
953
|
+
const targetPath = getEnvPath(envPath);
|
|
954
|
+
if (!existsSync2(targetPath)) {
|
|
955
|
+
return {};
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
const content = readFileSync2(targetPath, "utf-8");
|
|
959
|
+
const vars = {};
|
|
960
|
+
for (const line of content.split("\n")) {
|
|
961
|
+
const trimmed = line.trim();
|
|
962
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const eqIndex = trimmed.indexOf("=");
|
|
966
|
+
if (eqIndex > 0) {
|
|
967
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
968
|
+
let value = trimmed.substring(eqIndex + 1).trim();
|
|
969
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
970
|
+
value = value.slice(1, -1);
|
|
971
|
+
}
|
|
972
|
+
vars[key] = value;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return vars;
|
|
976
|
+
} catch {
|
|
977
|
+
return {};
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function saveConfiguration(providerConfigs, defaults, currentConfig) {
|
|
981
|
+
const envVars = buildEnvVars(providerConfigs);
|
|
982
|
+
let config2 = currentConfig || loadConfig();
|
|
983
|
+
config2.defaultProvider = defaults.provider;
|
|
984
|
+
config2.defaultModel = defaults.model;
|
|
985
|
+
config2.configuredProviders = providerConfigs.map((c) => c.key);
|
|
986
|
+
try {
|
|
987
|
+
const configDir = getConfigDir2();
|
|
988
|
+
if (!existsSync2(configDir)) {
|
|
989
|
+
mkdirSync2(configDir, { recursive: true });
|
|
990
|
+
}
|
|
991
|
+
const configPath = getConfigPath();
|
|
992
|
+
writeFileSync2(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
993
|
+
} catch (error) {
|
|
994
|
+
const classified = classifyError(error);
|
|
995
|
+
throw setupConfigWriteError(classified.message, classified.hint);
|
|
996
|
+
}
|
|
997
|
+
if (existsSync2(getEnvPath())) {
|
|
998
|
+
mergeEnvFile(envVars);
|
|
999
|
+
} else {
|
|
1000
|
+
saveEnvFile(envVars);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function displaySuccess(providerConfigs, defaults) {
|
|
1004
|
+
console.clear();
|
|
1005
|
+
printHeader("Setup Complete!");
|
|
1006
|
+
console.log(chalk2.green("\u2713") + " Configuration saved successfully!");
|
|
1007
|
+
console.log("");
|
|
1008
|
+
console.log(chalk2.bold("Configured Providers:"));
|
|
1009
|
+
for (const config2 of providerConfigs) {
|
|
1010
|
+
const status = config2.validated ? chalk2.green("\u2713") : chalk2.yellow("\u26A0");
|
|
1011
|
+
console.log(` ${status} ${config2.name}`);
|
|
1012
|
+
}
|
|
1013
|
+
console.log("");
|
|
1014
|
+
console.log(chalk2.bold("Default Configuration:"));
|
|
1015
|
+
console.log(` Provider: ${chalk2.cyan(defaults.provider)}`);
|
|
1016
|
+
console.log(` Model: ${chalk2.cyan(defaults.model)}`);
|
|
1017
|
+
console.log("");
|
|
1018
|
+
console.log(chalk2.bold("Next Steps:"));
|
|
1019
|
+
console.log(" Run " + chalk2.cyan("claude-mode") + " to start using Claude Code");
|
|
1020
|
+
console.log(" Run " + chalk2.cyan("claude-mode --list") + " to see all available models");
|
|
1021
|
+
console.log(" Run " + chalk2.cyan('claude-mode -p "your prompt"') + " for headless mode");
|
|
1022
|
+
console.log("");
|
|
1023
|
+
console.log(chalk2.gray("Configuration files:"));
|
|
1024
|
+
console.log(chalk2.gray(` Config: ${getConfigPath()}`));
|
|
1025
|
+
console.log(chalk2.gray(` Env: ${getEnvPath()}`));
|
|
1026
|
+
console.log("");
|
|
1027
|
+
}
|
|
1028
|
+
async function runSetup(options = {}) {
|
|
1029
|
+
try {
|
|
1030
|
+
const { force = false, provider: forceProvider, skipValidation = false } = options;
|
|
1031
|
+
const status = detectSetupStatus();
|
|
1032
|
+
const isFirstTime = status === "first-time";
|
|
1033
|
+
if (!force && !isFirstTime) {
|
|
1034
|
+
const shouldContinue = await displayUpdatePrompt();
|
|
1035
|
+
if (!shouldContinue) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
await displayWelcome(isFirstTime);
|
|
1040
|
+
const providerKeys = await selectProviders(forceProvider);
|
|
1041
|
+
const providerConfigs = [];
|
|
1042
|
+
const currentConfig = loadConfig();
|
|
1043
|
+
for (const providerKey of providerKeys) {
|
|
1044
|
+
try {
|
|
1045
|
+
const config2 = await configureProvider(providerKey, skipValidation);
|
|
1046
|
+
providerConfigs.push(config2);
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
console.log("");
|
|
1049
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1050
|
+
console.log(chalk2.yellow(`Failed to configure ${providerKey}: ${errorMessage}`));
|
|
1051
|
+
const shouldContinue = await confirm({
|
|
1052
|
+
message: "Continue with remaining providers?",
|
|
1053
|
+
default: true
|
|
1054
|
+
});
|
|
1055
|
+
if (!shouldContinue) {
|
|
1056
|
+
throw setupCancelledError();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (providerConfigs.length === 0) {
|
|
1061
|
+
throw setupIncompleteError("No providers were configured");
|
|
1062
|
+
}
|
|
1063
|
+
let currentDefaults;
|
|
1064
|
+
if (currentConfig.defaultProvider && currentConfig.defaultModel) {
|
|
1065
|
+
currentDefaults = {
|
|
1066
|
+
provider: currentConfig.defaultProvider,
|
|
1067
|
+
model: currentConfig.defaultModel
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const configuredProviderKeys = providerConfigs.map((c) => c.key);
|
|
1071
|
+
const defaults = await selectDefaults(configuredProviderKeys, currentDefaults);
|
|
1072
|
+
const confirmed = await reviewAndConfirm(providerConfigs, defaults, currentConfig);
|
|
1073
|
+
if (!confirmed) {
|
|
1074
|
+
throw setupCancelledError();
|
|
1075
|
+
}
|
|
1076
|
+
console.log("");
|
|
1077
|
+
console.log(chalk2.gray("Saving configuration..."));
|
|
1078
|
+
saveConfiguration(providerConfigs, defaults, currentConfig);
|
|
1079
|
+
displaySuccess(providerConfigs, defaults);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
const classified = classifyError(error);
|
|
1082
|
+
if (classified.message === "Setup cancelled") {
|
|
1083
|
+
console.log("");
|
|
1084
|
+
console.log(chalk2.yellow("Setup cancelled."));
|
|
1085
|
+
console.log("Run " + chalk2.cyan("claude-mode setup") + " to try again.");
|
|
1086
|
+
console.log("");
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
console.log("");
|
|
1090
|
+
console.log(chalk2.red("Setup failed:"));
|
|
1091
|
+
printError(classified);
|
|
1092
|
+
console.log("");
|
|
1093
|
+
console.log(chalk2.gray("You can run " + chalk2.cyan("claude-mode setup") + " to try again."));
|
|
1094
|
+
console.log("");
|
|
1095
|
+
process.exit(1);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/index.ts
|
|
1100
|
+
config();
|
|
1101
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
1102
|
+
var __dirname = path3.dirname(__filename);
|
|
1103
|
+
config({ path: path3.join(__dirname, "..", ".env") });
|
|
1104
|
+
var program = new Command();
|
|
1105
|
+
function printHeader2(text) {
|
|
1106
|
+
console.log("");
|
|
1107
|
+
console.log(chalk3.bold.blue("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
1108
|
+
console.log(chalk3.bold.blue("\u2551") + " " + chalk3.cyan(text));
|
|
1109
|
+
console.log(chalk3.bold.blue("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
1110
|
+
console.log("");
|
|
1111
|
+
}
|
|
524
1112
|
function printConfig(provider, modelId, mode) {
|
|
525
|
-
console.log(
|
|
526
|
-
console.log(
|
|
527
|
-
console.log(
|
|
528
|
-
console.log(
|
|
1113
|
+
console.log(chalk3.cyan("Provider:") + " " + provider.name);
|
|
1114
|
+
console.log(chalk3.cyan("Model:") + " " + modelId);
|
|
1115
|
+
console.log(chalk3.cyan("Base URL:") + " " + provider.getBaseUrl());
|
|
1116
|
+
console.log(chalk3.cyan("Mode:") + " " + mode);
|
|
529
1117
|
console.log("");
|
|
530
1118
|
}
|
|
1119
|
+
function shouldTriggerSetup() {
|
|
1120
|
+
const status = detectSetupStatus();
|
|
1121
|
+
if (status === "first-time") {
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
if (status === "incomplete") {
|
|
1125
|
+
return true;
|
|
1126
|
+
}
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
531
1129
|
function isClaudeInstalled() {
|
|
532
1130
|
try {
|
|
533
1131
|
execSync("which claude", { stdio: "ignore" });
|
|
@@ -602,16 +1200,16 @@ function formatHealthStatus(result) {
|
|
|
602
1200
|
const name = provider?.name || result.provider;
|
|
603
1201
|
if (result.healthy) {
|
|
604
1202
|
const latency = result.latencyMs ? ` (${result.latencyMs}ms)` : "";
|
|
605
|
-
return `${
|
|
1203
|
+
return `${chalk3.green("\u2713")} ${name}${chalk3.gray(latency)}`;
|
|
606
1204
|
} else {
|
|
607
1205
|
const error = result.error?.message || "unavailable";
|
|
608
|
-
return `${
|
|
1206
|
+
return `${chalk3.red("\u2717")} ${name} ${chalk3.gray(`- ${error}`)}`;
|
|
609
1207
|
}
|
|
610
1208
|
}
|
|
611
1209
|
async function displayHealthCheck() {
|
|
612
1210
|
const skipHealthCheck = getConfig("skipHealthCheck");
|
|
613
1211
|
if (skipHealthCheck) return;
|
|
614
|
-
console.log(
|
|
1212
|
+
console.log(chalk3.gray("Checking provider availability..."));
|
|
615
1213
|
const results = await checkAllProvidersHealth();
|
|
616
1214
|
console.log("");
|
|
617
1215
|
for (const result of results) {
|
|
@@ -621,10 +1219,10 @@ async function displayHealthCheck() {
|
|
|
621
1219
|
}
|
|
622
1220
|
async function interactiveMode(skipPermissions) {
|
|
623
1221
|
console.clear();
|
|
624
|
-
|
|
1222
|
+
printHeader2("Claude Mode");
|
|
625
1223
|
await displayHealthCheck();
|
|
626
1224
|
const allProviders = getProviders();
|
|
627
|
-
const providerKey = await
|
|
1225
|
+
const providerKey = await select2({
|
|
628
1226
|
message: "Select Provider:",
|
|
629
1227
|
choices: getProviderKeys().map((key) => ({
|
|
630
1228
|
name: allProviders[key].name,
|
|
@@ -633,20 +1231,20 @@ async function interactiveMode(skipPermissions) {
|
|
|
633
1231
|
}))
|
|
634
1232
|
});
|
|
635
1233
|
const provider = getProvider(providerKey);
|
|
636
|
-
console.log(
|
|
1234
|
+
console.log(chalk3.yellow("\u2192 Selected:") + " " + provider.name);
|
|
637
1235
|
const providerModels = await getModels(providerKey);
|
|
638
1236
|
if (providerModels.length === 0) {
|
|
639
|
-
console.log(
|
|
640
|
-
console.log(
|
|
641
|
-
const modelId2 = await
|
|
1237
|
+
console.log(chalk3.yellow("No models available for this provider."));
|
|
1238
|
+
console.log(chalk3.gray("You can enter a model ID manually."));
|
|
1239
|
+
const modelId2 = await input2({
|
|
642
1240
|
message: "Enter model ID:",
|
|
643
1241
|
validate: (value) => value.length > 0 ? true : "Model ID cannot be empty"
|
|
644
1242
|
});
|
|
645
|
-
console.log(
|
|
1243
|
+
console.log(chalk3.yellow("\u2192 Model:") + " " + modelId2);
|
|
646
1244
|
await continueInteractiveMode(provider, modelId2, skipPermissions);
|
|
647
1245
|
return;
|
|
648
1246
|
}
|
|
649
|
-
const modelId = await
|
|
1247
|
+
const modelId = await select2({
|
|
650
1248
|
message: "Select Model:",
|
|
651
1249
|
choices: providerModels.map((model) => ({
|
|
652
1250
|
name: model.name,
|
|
@@ -655,11 +1253,11 @@ async function interactiveMode(skipPermissions) {
|
|
|
655
1253
|
}))
|
|
656
1254
|
});
|
|
657
1255
|
const selectedModel = providerModels.find((m) => m.id === modelId);
|
|
658
|
-
console.log(
|
|
1256
|
+
console.log(chalk3.yellow("\u2192 Selected:") + " " + selectedModel?.name + ` (${modelId})`);
|
|
659
1257
|
await continueInteractiveMode(provider, modelId, skipPermissions);
|
|
660
1258
|
}
|
|
661
1259
|
async function continueInteractiveMode(provider, modelId, skipPermissions) {
|
|
662
|
-
const mode = await
|
|
1260
|
+
const mode = await select2({
|
|
663
1261
|
message: "Select Mode:",
|
|
664
1262
|
choices: [
|
|
665
1263
|
{ name: "Terminal (interactive)", value: "terminal" },
|
|
@@ -668,13 +1266,13 @@ async function continueInteractiveMode(provider, modelId, skipPermissions) {
|
|
|
668
1266
|
});
|
|
669
1267
|
let prompt;
|
|
670
1268
|
if (mode === "headless") {
|
|
671
|
-
prompt = await
|
|
1269
|
+
prompt = await input2({
|
|
672
1270
|
message: "Enter your prompt:",
|
|
673
1271
|
validate: (value) => value.length > 0 ? true : "Prompt cannot be empty"
|
|
674
1272
|
});
|
|
675
1273
|
}
|
|
676
1274
|
if (!skipPermissions) {
|
|
677
|
-
skipPermissions = await
|
|
1275
|
+
skipPermissions = await select2({
|
|
678
1276
|
message: "Skip permission prompts when executing commands?",
|
|
679
1277
|
choices: [
|
|
680
1278
|
{ name: "No (ask for permission)", value: false },
|
|
@@ -683,19 +1281,19 @@ async function continueInteractiveMode(provider, modelId, skipPermissions) {
|
|
|
683
1281
|
});
|
|
684
1282
|
}
|
|
685
1283
|
console.log(
|
|
686
|
-
|
|
1284
|
+
chalk3.yellow("\u2192 Skip permissions:") + " " + (skipPermissions ? chalk3.red("Yes (\u26A0\uFE0F auto-approve)") : chalk3.green("No (ask)"))
|
|
687
1285
|
);
|
|
688
1286
|
const isHeadless = mode === "headless";
|
|
689
1287
|
if (isHeadless) {
|
|
690
|
-
|
|
1288
|
+
printHeader2("Executing Claude Code (Headless)");
|
|
691
1289
|
} else {
|
|
692
|
-
|
|
1290
|
+
printHeader2("Launching Claude Code (Interactive)");
|
|
693
1291
|
}
|
|
694
1292
|
printConfig(provider, modelId, isHeadless ? "Headless" : "Interactive");
|
|
695
1293
|
try {
|
|
696
1294
|
await runClaude(provider, modelId, isHeadless, skipPermissions, prompt);
|
|
697
1295
|
} catch (error) {
|
|
698
|
-
console.error(
|
|
1296
|
+
console.error(chalk3.red("Error:"), error);
|
|
699
1297
|
process.exit(1);
|
|
700
1298
|
}
|
|
701
1299
|
}
|
|
@@ -703,8 +1301,8 @@ async function quickMode(providerArg, modelArg, skipPermissions, modeArg, prompt
|
|
|
703
1301
|
const providerKey = resolveProvider(providerArg);
|
|
704
1302
|
const provider = getProvider(providerKey);
|
|
705
1303
|
if (!provider) {
|
|
706
|
-
console.error(
|
|
707
|
-
console.log(
|
|
1304
|
+
console.error(chalk3.red(`Unknown provider: ${providerArg}`));
|
|
1305
|
+
console.log(chalk3.gray("Available providers: " + getProviderKeys().join(", ")));
|
|
708
1306
|
process.exit(1);
|
|
709
1307
|
}
|
|
710
1308
|
const modelId = await resolveModel(providerKey, modelArg);
|
|
@@ -729,7 +1327,7 @@ async function quickMode(providerArg, modelArg, skipPermissions, modeArg, prompt
|
|
|
729
1327
|
}
|
|
730
1328
|
}
|
|
731
1329
|
if (isHeadless && !prompt) {
|
|
732
|
-
prompt = await
|
|
1330
|
+
prompt = await input2({
|
|
733
1331
|
message: "Enter your prompt:",
|
|
734
1332
|
validate: (value) => value.length > 0 ? true : "Prompt cannot be empty"
|
|
735
1333
|
});
|
|
@@ -738,23 +1336,23 @@ async function quickMode(providerArg, modelArg, skipPermissions, modeArg, prompt
|
|
|
738
1336
|
try {
|
|
739
1337
|
await runClaude(provider, modelId, isHeadless, skipPermissions, prompt);
|
|
740
1338
|
} catch (error) {
|
|
741
|
-
console.error(
|
|
1339
|
+
console.error(chalk3.red("Error:"), error);
|
|
742
1340
|
process.exit(1);
|
|
743
1341
|
}
|
|
744
1342
|
}
|
|
745
1343
|
async function listModels() {
|
|
746
|
-
console.log(
|
|
1344
|
+
console.log(chalk3.bold("\nAvailable Models by Provider:\n"));
|
|
747
1345
|
const allProviders = getProviders();
|
|
748
1346
|
for (const providerKey of getProviderKeys()) {
|
|
749
1347
|
const provider = allProviders[providerKey];
|
|
750
1348
|
const providerModels = await getModels(providerKey);
|
|
751
|
-
console.log(
|
|
752
|
-
console.log(
|
|
1349
|
+
console.log(chalk3.cyan(`${provider.name}:`));
|
|
1350
|
+
console.log(chalk3.gray(` ${provider.getDescription()}`));
|
|
753
1351
|
if (providerModels.length === 0) {
|
|
754
|
-
console.log(` ${
|
|
1352
|
+
console.log(` ${chalk3.gray("(no models found)")}`);
|
|
755
1353
|
} else {
|
|
756
1354
|
for (const model of providerModels) {
|
|
757
|
-
console.log(` ${
|
|
1355
|
+
console.log(` ${chalk3.green(model.shortcut.padEnd(14))} \u2192 ${model.id}`);
|
|
758
1356
|
}
|
|
759
1357
|
}
|
|
760
1358
|
console.log("");
|
|
@@ -762,14 +1360,14 @@ async function listModels() {
|
|
|
762
1360
|
}
|
|
763
1361
|
function showConfig() {
|
|
764
1362
|
const config2 = loadConfig();
|
|
765
|
-
console.log(
|
|
1363
|
+
console.log(chalk3.bold("\nCurrent Configuration:\n"));
|
|
766
1364
|
console.log(JSON.stringify(config2, null, 2));
|
|
767
1365
|
console.log("");
|
|
768
1366
|
}
|
|
769
1367
|
function initConfigCommand() {
|
|
770
1368
|
const configPath = initConfig();
|
|
771
|
-
console.log(
|
|
772
|
-
console.log(
|
|
1369
|
+
console.log(chalk3.green(`Config file created at: ${configPath}`));
|
|
1370
|
+
console.log(chalk3.gray("Edit this file to customize your settings."));
|
|
773
1371
|
}
|
|
774
1372
|
function generateBashCompletion() {
|
|
775
1373
|
const providerKeys = getProviderKeys();
|
|
@@ -897,13 +1495,13 @@ function printCompletion(shell) {
|
|
|
897
1495
|
console.log(generateFishCompletion());
|
|
898
1496
|
break;
|
|
899
1497
|
default:
|
|
900
|
-
console.error(
|
|
901
|
-
console.log(
|
|
1498
|
+
console.error(chalk3.red(`Unknown shell: ${shell}`));
|
|
1499
|
+
console.log(chalk3.gray("Supported shells: bash, zsh, fish"));
|
|
902
1500
|
process.exit(1);
|
|
903
1501
|
}
|
|
904
1502
|
}
|
|
905
1503
|
async function healthCommand() {
|
|
906
|
-
console.log(
|
|
1504
|
+
console.log(chalk3.bold("\nProvider Health Check:\n"));
|
|
907
1505
|
const results = await checkAllProvidersHealth();
|
|
908
1506
|
for (const result of results) {
|
|
909
1507
|
console.log(" " + formatHealthStatus(result));
|
|
@@ -912,40 +1510,40 @@ async function healthCommand() {
|
|
|
912
1510
|
const healthy = results.filter((r) => r.healthy).length;
|
|
913
1511
|
const total = results.length;
|
|
914
1512
|
if (healthy === total) {
|
|
915
|
-
console.log(
|
|
1513
|
+
console.log(chalk3.green(`All ${total} providers are healthy.`));
|
|
916
1514
|
} else {
|
|
917
|
-
console.log(
|
|
1515
|
+
console.log(chalk3.yellow(`${healthy}/${total} providers are healthy.`));
|
|
918
1516
|
}
|
|
919
1517
|
}
|
|
920
1518
|
program.name("claude-mode").description(
|
|
921
1519
|
`
|
|
922
|
-
${
|
|
1520
|
+
${chalk3.bold("Claude Mode")} - Launch Claude Code with different providers
|
|
923
1521
|
|
|
924
|
-
${
|
|
1522
|
+
${chalk3.bold("Providers:")}
|
|
925
1523
|
openrouter OpenRouter API
|
|
926
1524
|
ollama-cloud Ollama Cloud (OLLAMA_HOST)
|
|
927
1525
|
ollama-local Ollama Local (OLLAMA_BASE_URL_LOCAL)
|
|
928
1526
|
ollama-custom Ollama Custom (OLLAMA_BASE_URL_CUSTOM)
|
|
929
1527
|
|
|
930
|
-
${
|
|
1528
|
+
${chalk3.bold("Model shortcuts:")}
|
|
931
1529
|
OpenRouter (Premium): gpt52, gpt52-pro, gpt52-codex, opus, grok
|
|
932
1530
|
OpenRouter (Value): deepseek, zai-glm47-flash, seed16, sonnet, haiku
|
|
933
1531
|
OpenRouter (Existing): gpt120, glm47, gemini-pro, gemini-flash
|
|
934
1532
|
Ollama: Models discovered dynamically via API
|
|
935
1533
|
|
|
936
|
-
${
|
|
1534
|
+
${chalk3.bold("Mode shortcuts:")}
|
|
937
1535
|
terminal, t, interactive, i - Interactive terminal mode
|
|
938
1536
|
headless, h, prompt, p - Headless mode with prompt
|
|
939
1537
|
|
|
940
|
-
${
|
|
1538
|
+
${chalk3.bold("Options:")}
|
|
941
1539
|
-p, --prompt <prompt> Headless mode (uses defaults if provider/model not set)
|
|
942
1540
|
-d, --dangerously-skip-permissions Skip permission prompts (\u26A0\uFE0F use with caution)
|
|
943
1541
|
|
|
944
|
-
${
|
|
1542
|
+
${chalk3.bold("Default Provider/Model:")}
|
|
945
1543
|
Set in ~/.claude-mode/claude-mode.json:
|
|
946
1544
|
{ "defaultProvider": "openrouter", "defaultModel": "sonnet" }
|
|
947
1545
|
|
|
948
|
-
${
|
|
1546
|
+
${chalk3.bold("Examples:")}
|
|
949
1547
|
claude-mode Interactive menu
|
|
950
1548
|
claude-mode -p "perform a code review" Headless with default provider/model
|
|
951
1549
|
claude-mode openrouter sonnet Interactive with Claude Sonnet
|
|
@@ -978,7 +1576,24 @@ program.command("config").description("Manage configuration").argument("[action]
|
|
|
978
1576
|
program.command("completion").description("Generate shell completion scripts").argument("<shell>", "Shell type: bash, zsh, fish").action((shell) => {
|
|
979
1577
|
printCompletion(shell);
|
|
980
1578
|
});
|
|
1579
|
+
program.command("setup").description("Interactive setup wizard for first-time configuration").option("-f, --force", "Re-run setup even if config exists").option("-p, --provider <provider>", "Configure only this provider").option("--skip-validation", "Skip API key validation").action(async (options) => {
|
|
1580
|
+
await runSetup({
|
|
1581
|
+
force: options.force,
|
|
1582
|
+
provider: options.provider,
|
|
1583
|
+
skipValidation: options.skipValidation
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
981
1586
|
program.option("-d, --dangerously-skip-permissions", "Skip permission prompts when executing commands").option("-l, --list", "List all available providers and models").option("-p, --prompt <prompt>", "Run in headless mode with the given prompt (uses defaults if provider/model not specified)").argument("[provider]", "Provider (openrouter, ollama-cloud, ollama-local, ollama-custom)").argument("[model]", "Model ID or shortcut").argument("[mode]", "Mode (terminal/t, headless/h) or prompt for headless").argument("[promptArg]", "Prompt for headless mode").action(async (provider, model, mode, promptArg, options) => {
|
|
1587
|
+
if (shouldTriggerSetup()) {
|
|
1588
|
+
const shouldSetup = await confirm2({
|
|
1589
|
+
message: "No configuration found. Would you like to run setup?",
|
|
1590
|
+
default: true
|
|
1591
|
+
});
|
|
1592
|
+
if (shouldSetup) {
|
|
1593
|
+
await runSetup({ force: false });
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
982
1597
|
if (options.list) {
|
|
983
1598
|
await listModels();
|
|
984
1599
|
return;
|
|
@@ -990,14 +1605,14 @@ program.option("-d, --dangerously-skip-permissions", "Skip permission prompts wh
|
|
|
990
1605
|
const effectiveProvider = provider || defaultProvider;
|
|
991
1606
|
const effectiveModel = model || defaultModel;
|
|
992
1607
|
if (!effectiveProvider || !effectiveModel) {
|
|
993
|
-
console.error(
|
|
1608
|
+
console.error(chalk3.red("Error: Provider and model are required"));
|
|
994
1609
|
if (!effectiveProvider && !effectiveModel) {
|
|
995
|
-
console.log(
|
|
996
|
-
console.log(
|
|
1610
|
+
console.log(chalk3.gray("Set defaults in config: claude-mode config init"));
|
|
1611
|
+
console.log(chalk3.gray("Then edit ~/.claude-mode/claude-mode.json to set defaultProvider and defaultModel"));
|
|
997
1612
|
} else if (!effectiveProvider) {
|
|
998
|
-
console.log(
|
|
1613
|
+
console.log(chalk3.gray("Missing: provider (set defaultProvider in config or pass as argument)"));
|
|
999
1614
|
} else {
|
|
1000
|
-
console.log(
|
|
1615
|
+
console.log(chalk3.gray("Missing: model (set defaultModel in config or pass as argument)"));
|
|
1001
1616
|
}
|
|
1002
1617
|
process.exit(1);
|
|
1003
1618
|
}
|
|
@@ -1011,10 +1626,10 @@ program.option("-d, --dangerously-skip-permissions", "Skip permission prompts wh
|
|
|
1011
1626
|
if (defaultModel) {
|
|
1012
1627
|
await quickMode(provider, defaultModel, skipPermissions, mode, promptArg);
|
|
1013
1628
|
} else {
|
|
1014
|
-
console.error(
|
|
1015
|
-
console.log(
|
|
1016
|
-
console.log(
|
|
1017
|
-
console.log(
|
|
1629
|
+
console.error(chalk3.red("Error: Model is required when provider is specified"));
|
|
1630
|
+
console.log(chalk3.gray("Usage: claude-mode <provider> <model> [mode] [prompt]"));
|
|
1631
|
+
console.log(chalk3.gray("Or set defaultModel in ~/.claude-mode/claude-mode.json"));
|
|
1632
|
+
console.log(chalk3.gray("Run claude-mode --list to see available models"));
|
|
1018
1633
|
process.exit(1);
|
|
1019
1634
|
}
|
|
1020
1635
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cbuk100011/claude-mode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "CLI launcher for Claude Code with multiple AI providers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"typecheck": "tsc --noEmit"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@cbuk100011/claude-mode": "^1.3.0",
|
|
30
31
|
"@inquirer/prompts": "^7.2.1",
|
|
31
32
|
"chalk": "^5.4.1",
|
|
32
33
|
"commander": "^13.1.0",
|