@cbuk100011/claude-mode 1.2.0 → 1.3.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 +681 -65
- package/package.json +1 -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 = {
|
|
@@ -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,539 @@ 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
|
+
const defaultUrl = existingUrl || "http://192.168.86.101:11434";
|
|
716
|
+
config2.customUrl = await input({
|
|
717
|
+
message: "Enter Ollama URL:",
|
|
718
|
+
default: defaultUrl,
|
|
719
|
+
validate: (value) => {
|
|
720
|
+
try {
|
|
721
|
+
new URL(value);
|
|
722
|
+
return true;
|
|
723
|
+
} catch {
|
|
724
|
+
return "Please enter a valid URL";
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
if (config2.customUrl) {
|
|
729
|
+
process.env.OLLAMA_BASE_URL_CUSTOM = config2.customUrl;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (!skipValidation) {
|
|
733
|
+
config2.validated = await validateProvider(providerKey);
|
|
734
|
+
} else {
|
|
735
|
+
config2.validated = true;
|
|
736
|
+
}
|
|
737
|
+
return config2;
|
|
738
|
+
}
|
|
739
|
+
async function validateProvider(providerKey) {
|
|
740
|
+
console.log(chalk2.gray("Validating provider connection..."));
|
|
741
|
+
const result = await checkProviderHealth(providerKey);
|
|
742
|
+
if (result.healthy) {
|
|
743
|
+
const latency = result.latencyMs ? ` (${result.latencyMs}ms)` : "";
|
|
744
|
+
console.log(chalk2.green(`\u2713 ${providerKey} is reachable${latency}`));
|
|
745
|
+
return true;
|
|
746
|
+
} else {
|
|
747
|
+
console.log(chalk2.yellow(`\u26A0 ${providerKey} validation failed`));
|
|
748
|
+
if (result.error) {
|
|
749
|
+
console.log(chalk2.gray(` ${result.error.message}`));
|
|
750
|
+
}
|
|
751
|
+
const retry = await confirm({
|
|
752
|
+
message: "Continue anyway?",
|
|
753
|
+
default: false
|
|
754
|
+
});
|
|
755
|
+
return retry;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async function selectDefaults(configuredProviders, currentDefaults) {
|
|
759
|
+
printSection("Set Defaults");
|
|
760
|
+
let defaultProvider;
|
|
761
|
+
let defaultModel;
|
|
762
|
+
if (currentDefaults && configuredProviders.includes(currentDefaults.provider)) {
|
|
763
|
+
const useCurrent = await confirm({
|
|
764
|
+
message: `Keep default provider as ${currentDefaults.provider}?`,
|
|
765
|
+
default: true
|
|
766
|
+
});
|
|
767
|
+
if (useCurrent) {
|
|
768
|
+
defaultProvider = currentDefaults.provider;
|
|
769
|
+
} else {
|
|
770
|
+
defaultProvider = await select({
|
|
771
|
+
message: "Select default provider:",
|
|
772
|
+
choices: configuredProviders.map((key) => ({
|
|
773
|
+
name: getProvider(key)?.name || key,
|
|
774
|
+
value: key
|
|
775
|
+
}))
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
defaultProvider = await select({
|
|
780
|
+
message: "Select default provider:",
|
|
781
|
+
choices: configuredProviders.map((key) => ({
|
|
782
|
+
name: getProvider(key)?.name || key,
|
|
783
|
+
value: key
|
|
784
|
+
}))
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
const models3 = await getModels(defaultProvider);
|
|
788
|
+
console.log("");
|
|
789
|
+
console.log(chalk2.gray("Available models for " + getProvider(defaultProvider)?.name + ":"));
|
|
790
|
+
if (models3.length === 0) {
|
|
791
|
+
console.log(" " + chalk2.gray("(no models found - will use manual input)"));
|
|
792
|
+
console.log("");
|
|
793
|
+
const modelInput = await input({
|
|
794
|
+
message: "Enter default model ID:",
|
|
795
|
+
validate: (value) => value.length > 0 ? true : "Model ID cannot be empty"
|
|
796
|
+
});
|
|
797
|
+
defaultModel = modelInput;
|
|
798
|
+
} else {
|
|
799
|
+
for (const model of models3) {
|
|
800
|
+
console.log(` ${chalk2.green(model.shortcut.padEnd(20))} ${chalk2.gray(model.id)}`);
|
|
801
|
+
}
|
|
802
|
+
console.log("");
|
|
803
|
+
defaultModel = await select({
|
|
804
|
+
message: "Select default model:",
|
|
805
|
+
choices: models3.map((model) => ({
|
|
806
|
+
name: `${model.name} (${model.shortcut})`,
|
|
807
|
+
value: model.id
|
|
808
|
+
}))
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
return { provider: defaultProvider, model: defaultModel };
|
|
812
|
+
}
|
|
813
|
+
function formatProviderConfig(config2) {
|
|
814
|
+
const lines = [];
|
|
815
|
+
lines.push(chalk2.cyan(` ${config2.name} (${config2.key})`));
|
|
816
|
+
if (config2.apiKey) {
|
|
817
|
+
const masked = config2.apiKey.substring(0, 8) + "..." + config2.apiKey.slice(-4);
|
|
818
|
+
lines.push(` API Key: ${chalk2.gray(masked)}`);
|
|
819
|
+
}
|
|
820
|
+
if (config2.customUrl) {
|
|
821
|
+
lines.push(` URL: ${chalk2.gray(config2.customUrl)}`);
|
|
822
|
+
}
|
|
823
|
+
if (config2.validated) {
|
|
824
|
+
lines.push(` Status: ${chalk2.green("\u2713 Validated")}`);
|
|
825
|
+
} else {
|
|
826
|
+
lines.push(` Status: ${chalk2.yellow("\u26A0 Not validated")}`);
|
|
827
|
+
}
|
|
828
|
+
return lines.join("\n");
|
|
829
|
+
}
|
|
830
|
+
async function reviewAndConfirm(providerConfigs, defaults, currentConfig) {
|
|
831
|
+
printSection("Review Configuration");
|
|
832
|
+
console.log(chalk2.bold("Configured Providers:"));
|
|
833
|
+
console.log("");
|
|
834
|
+
for (const config2 of providerConfigs) {
|
|
835
|
+
console.log(formatProviderConfig(config2));
|
|
836
|
+
}
|
|
837
|
+
console.log("");
|
|
838
|
+
console.log(chalk2.bold("Defaults:"));
|
|
839
|
+
console.log(` ${chalk2.cyan("Provider:")} ${defaults.provider}`);
|
|
840
|
+
console.log(` ${chalk2.cyan("Model:")} ${defaults.model}`);
|
|
841
|
+
console.log("");
|
|
842
|
+
console.log(chalk2.bold("Files to be created/updated:"));
|
|
843
|
+
console.log(` ${chalk2.cyan("Config:")} ${getConfigPath()}`);
|
|
844
|
+
console.log(` ${chalk2.cyan("Env:")} ${getEnvPath()}`);
|
|
845
|
+
console.log("");
|
|
846
|
+
const confirmed = await confirm({
|
|
847
|
+
message: "Save this configuration?",
|
|
848
|
+
default: true
|
|
849
|
+
});
|
|
850
|
+
return confirmed;
|
|
851
|
+
}
|
|
852
|
+
function buildEnvVars(providerConfigs) {
|
|
853
|
+
const envVars = {};
|
|
854
|
+
for (const config2 of providerConfigs) {
|
|
855
|
+
switch (config2.key) {
|
|
856
|
+
case "openrouter":
|
|
857
|
+
if (config2.apiKey) {
|
|
858
|
+
envVars.ANTHROPIC_BASE_URL = "https://openrouter.ai/api";
|
|
859
|
+
envVars.ANTHROPIC_AUTH_TOKEN = config2.apiKey;
|
|
860
|
+
envVars.OPEN_ROUTER_API_KEY = config2.apiKey;
|
|
861
|
+
}
|
|
862
|
+
break;
|
|
863
|
+
case "ollama-cloud":
|
|
864
|
+
if (config2.apiKey) {
|
|
865
|
+
envVars.OLLAMA_HOST = "https://ollama.com";
|
|
866
|
+
envVars.OLLAMA_API_KEY = config2.apiKey;
|
|
867
|
+
}
|
|
868
|
+
break;
|
|
869
|
+
case "ollama-local":
|
|
870
|
+
envVars.OLLAMA_BASE_URL_LOCAL = "http://localhost:11434";
|
|
871
|
+
break;
|
|
872
|
+
case "ollama-custom":
|
|
873
|
+
if (config2.customUrl) {
|
|
874
|
+
envVars.OLLAMA_BASE_URL_CUSTOM = config2.customUrl;
|
|
875
|
+
}
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return envVars;
|
|
880
|
+
}
|
|
881
|
+
function formatEnvFile(envVars) {
|
|
882
|
+
const lines = [];
|
|
883
|
+
lines.push("# claude-mode configuration");
|
|
884
|
+
lines.push("# Generated by: claude-mode setup");
|
|
885
|
+
lines.push(`# Date: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
886
|
+
lines.push("");
|
|
887
|
+
if (envVars.ANTHROPIC_BASE_URL || envVars.ANTHROPIC_AUTH_TOKEN) {
|
|
888
|
+
lines.push("# OpenRouter");
|
|
889
|
+
if (envVars.ANTHROPIC_BASE_URL) {
|
|
890
|
+
lines.push(`ANTHROPIC_BASE_URL=${envVars.ANTHROPIC_BASE_URL}`);
|
|
891
|
+
}
|
|
892
|
+
if (envVars.ANTHROPIC_AUTH_TOKEN) {
|
|
893
|
+
lines.push(`ANTHROPIC_AUTH_TOKEN=${envVars.ANTHROPIC_AUTH_TOKEN}`);
|
|
894
|
+
}
|
|
895
|
+
if (envVars.OPEN_ROUTER_API_KEY) {
|
|
896
|
+
lines.push(`OPEN_ROUTER_API_KEY=${envVars.OPEN_ROUTER_API_KEY}`);
|
|
897
|
+
}
|
|
898
|
+
lines.push("");
|
|
899
|
+
}
|
|
900
|
+
if (envVars.OLLAMA_HOST || envVars.OLLAMA_API_KEY) {
|
|
901
|
+
lines.push("# Ollama Cloud");
|
|
902
|
+
if (envVars.OLLAMA_HOST) {
|
|
903
|
+
lines.push(`OLLAMA_HOST=${envVars.OLLAMA_HOST}`);
|
|
904
|
+
}
|
|
905
|
+
if (envVars.OLLAMA_API_KEY) {
|
|
906
|
+
lines.push(`OLLAMA_API_KEY=${envVars.OLLAMA_API_KEY}`);
|
|
907
|
+
}
|
|
908
|
+
lines.push("");
|
|
909
|
+
}
|
|
910
|
+
if (envVars.OLLAMA_BASE_URL_LOCAL) {
|
|
911
|
+
lines.push("# Ollama Local");
|
|
912
|
+
lines.push(`OLLAMA_BASE_URL_LOCAL=${envVars.OLLAMA_BASE_URL_LOCAL}`);
|
|
913
|
+
lines.push("");
|
|
914
|
+
}
|
|
915
|
+
if (envVars.OLLAMA_BASE_URL_CUSTOM) {
|
|
916
|
+
lines.push("# Ollama Custom");
|
|
917
|
+
lines.push(`OLLAMA_BASE_URL_CUSTOM=${envVars.OLLAMA_BASE_URL_CUSTOM}`);
|
|
918
|
+
lines.push("");
|
|
919
|
+
}
|
|
920
|
+
return lines.join("\n");
|
|
921
|
+
}
|
|
922
|
+
function mergeEnvFile(envVars, envPath) {
|
|
923
|
+
const targetPath = getEnvPath(envPath);
|
|
924
|
+
const existingVars = loadEnvFile(targetPath);
|
|
925
|
+
const merged = { ...existingVars, ...envVars };
|
|
926
|
+
const lines = [];
|
|
927
|
+
lines.push("# claude-mode configuration");
|
|
928
|
+
lines.push("# Generated by: claude-mode setup");
|
|
929
|
+
lines.push(`# Date: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
930
|
+
lines.push("");
|
|
931
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
932
|
+
if (!key.startsWith("_claude_mode_")) {
|
|
933
|
+
lines.push(`${key}=${value}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
try {
|
|
937
|
+
writeFileSync2(targetPath, lines.join("\n") + "\n", "utf-8");
|
|
938
|
+
} catch (error) {
|
|
939
|
+
const classified = classifyError(error);
|
|
940
|
+
throw setupEnvWriteError(classified.message, classified.hint);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function saveEnvFile(envVars, envPath) {
|
|
944
|
+
const targetPath = getEnvPath(envPath);
|
|
945
|
+
try {
|
|
946
|
+
const content = formatEnvFile(envVars);
|
|
947
|
+
writeFileSync2(targetPath, content + "\n", "utf-8");
|
|
948
|
+
} catch (error) {
|
|
949
|
+
const classified = classifyError(error);
|
|
950
|
+
throw setupEnvWriteError(classified.message, classified.hint);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function loadEnvFile(envPath) {
|
|
954
|
+
const targetPath = getEnvPath(envPath);
|
|
955
|
+
if (!existsSync2(targetPath)) {
|
|
956
|
+
return {};
|
|
957
|
+
}
|
|
958
|
+
try {
|
|
959
|
+
const content = readFileSync2(targetPath, "utf-8");
|
|
960
|
+
const vars = {};
|
|
961
|
+
for (const line of content.split("\n")) {
|
|
962
|
+
const trimmed = line.trim();
|
|
963
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
const eqIndex = trimmed.indexOf("=");
|
|
967
|
+
if (eqIndex > 0) {
|
|
968
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
969
|
+
let value = trimmed.substring(eqIndex + 1).trim();
|
|
970
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
971
|
+
value = value.slice(1, -1);
|
|
972
|
+
}
|
|
973
|
+
vars[key] = value;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return vars;
|
|
977
|
+
} catch {
|
|
978
|
+
return {};
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function saveConfiguration(providerConfigs, defaults, currentConfig) {
|
|
982
|
+
const envVars = buildEnvVars(providerConfigs);
|
|
983
|
+
let config2 = currentConfig || loadConfig();
|
|
984
|
+
config2.defaultProvider = defaults.provider;
|
|
985
|
+
config2.defaultModel = defaults.model;
|
|
986
|
+
config2.configuredProviders = providerConfigs.map((c) => c.key);
|
|
987
|
+
try {
|
|
988
|
+
const configDir = getConfigDir2();
|
|
989
|
+
if (!existsSync2(configDir)) {
|
|
990
|
+
mkdirSync2(configDir, { recursive: true });
|
|
991
|
+
}
|
|
992
|
+
const configPath = getConfigPath();
|
|
993
|
+
writeFileSync2(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
994
|
+
} catch (error) {
|
|
995
|
+
const classified = classifyError(error);
|
|
996
|
+
throw setupConfigWriteError(classified.message, classified.hint);
|
|
997
|
+
}
|
|
998
|
+
if (existsSync2(getEnvPath())) {
|
|
999
|
+
mergeEnvFile(envVars);
|
|
1000
|
+
} else {
|
|
1001
|
+
saveEnvFile(envVars);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
function displaySuccess(providerConfigs, defaults) {
|
|
1005
|
+
console.clear();
|
|
1006
|
+
printHeader("Setup Complete!");
|
|
1007
|
+
console.log(chalk2.green("\u2713") + " Configuration saved successfully!");
|
|
1008
|
+
console.log("");
|
|
1009
|
+
console.log(chalk2.bold("Configured Providers:"));
|
|
1010
|
+
for (const config2 of providerConfigs) {
|
|
1011
|
+
const status = config2.validated ? chalk2.green("\u2713") : chalk2.yellow("\u26A0");
|
|
1012
|
+
console.log(` ${status} ${config2.name}`);
|
|
1013
|
+
}
|
|
1014
|
+
console.log("");
|
|
1015
|
+
console.log(chalk2.bold("Default Configuration:"));
|
|
1016
|
+
console.log(` Provider: ${chalk2.cyan(defaults.provider)}`);
|
|
1017
|
+
console.log(` Model: ${chalk2.cyan(defaults.model)}`);
|
|
1018
|
+
console.log("");
|
|
1019
|
+
console.log(chalk2.bold("Next Steps:"));
|
|
1020
|
+
console.log(" Run " + chalk2.cyan("claude-mode") + " to start using Claude Code");
|
|
1021
|
+
console.log(" Run " + chalk2.cyan("claude-mode --list") + " to see all available models");
|
|
1022
|
+
console.log(" Run " + chalk2.cyan('claude-mode -p "your prompt"') + " for headless mode");
|
|
1023
|
+
console.log("");
|
|
1024
|
+
console.log(chalk2.gray("Configuration files:"));
|
|
1025
|
+
console.log(chalk2.gray(` Config: ${getConfigPath()}`));
|
|
1026
|
+
console.log(chalk2.gray(` Env: ${getEnvPath()}`));
|
|
1027
|
+
console.log("");
|
|
1028
|
+
}
|
|
1029
|
+
async function runSetup(options = {}) {
|
|
1030
|
+
try {
|
|
1031
|
+
const { force = false, provider: forceProvider, skipValidation = false } = options;
|
|
1032
|
+
const status = detectSetupStatus();
|
|
1033
|
+
const isFirstTime = status === "first-time";
|
|
1034
|
+
if (!force && !isFirstTime) {
|
|
1035
|
+
const shouldContinue = await displayUpdatePrompt();
|
|
1036
|
+
if (!shouldContinue) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
await displayWelcome(isFirstTime);
|
|
1041
|
+
const providerKeys = await selectProviders(forceProvider);
|
|
1042
|
+
const providerConfigs = [];
|
|
1043
|
+
const currentConfig = loadConfig();
|
|
1044
|
+
for (const providerKey of providerKeys) {
|
|
1045
|
+
try {
|
|
1046
|
+
const config2 = await configureProvider(providerKey, skipValidation);
|
|
1047
|
+
providerConfigs.push(config2);
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
console.log("");
|
|
1050
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1051
|
+
console.log(chalk2.yellow(`Failed to configure ${providerKey}: ${errorMessage}`));
|
|
1052
|
+
const shouldContinue = await confirm({
|
|
1053
|
+
message: "Continue with remaining providers?",
|
|
1054
|
+
default: true
|
|
1055
|
+
});
|
|
1056
|
+
if (!shouldContinue) {
|
|
1057
|
+
throw setupCancelledError();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
if (providerConfigs.length === 0) {
|
|
1062
|
+
throw setupIncompleteError("No providers were configured");
|
|
1063
|
+
}
|
|
1064
|
+
let currentDefaults;
|
|
1065
|
+
if (currentConfig.defaultProvider && currentConfig.defaultModel) {
|
|
1066
|
+
currentDefaults = {
|
|
1067
|
+
provider: currentConfig.defaultProvider,
|
|
1068
|
+
model: currentConfig.defaultModel
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
const configuredProviderKeys = providerConfigs.map((c) => c.key);
|
|
1072
|
+
const defaults = await selectDefaults(configuredProviderKeys, currentDefaults);
|
|
1073
|
+
const confirmed = await reviewAndConfirm(providerConfigs, defaults, currentConfig);
|
|
1074
|
+
if (!confirmed) {
|
|
1075
|
+
throw setupCancelledError();
|
|
1076
|
+
}
|
|
1077
|
+
console.log("");
|
|
1078
|
+
console.log(chalk2.gray("Saving configuration..."));
|
|
1079
|
+
saveConfiguration(providerConfigs, defaults, currentConfig);
|
|
1080
|
+
displaySuccess(providerConfigs, defaults);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
const classified = classifyError(error);
|
|
1083
|
+
if (classified.message === "Setup cancelled") {
|
|
1084
|
+
console.log("");
|
|
1085
|
+
console.log(chalk2.yellow("Setup cancelled."));
|
|
1086
|
+
console.log("Run " + chalk2.cyan("claude-mode setup") + " to try again.");
|
|
1087
|
+
console.log("");
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
console.log("");
|
|
1091
|
+
console.log(chalk2.red("Setup failed:"));
|
|
1092
|
+
printError(classified);
|
|
1093
|
+
console.log("");
|
|
1094
|
+
console.log(chalk2.gray("You can run " + chalk2.cyan("claude-mode setup") + " to try again."));
|
|
1095
|
+
console.log("");
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// src/index.ts
|
|
1101
|
+
config();
|
|
1102
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
1103
|
+
var __dirname = path3.dirname(__filename);
|
|
1104
|
+
config({ path: path3.join(__dirname, "..", ".env") });
|
|
1105
|
+
var program = new Command();
|
|
1106
|
+
function printHeader2(text) {
|
|
1107
|
+
console.log("");
|
|
1108
|
+
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"));
|
|
1109
|
+
console.log(chalk3.bold.blue("\u2551") + " " + chalk3.cyan(text));
|
|
1110
|
+
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"));
|
|
1111
|
+
console.log("");
|
|
1112
|
+
}
|
|
524
1113
|
function printConfig(provider, modelId, mode) {
|
|
525
|
-
console.log(
|
|
526
|
-
console.log(
|
|
527
|
-
console.log(
|
|
528
|
-
console.log(
|
|
1114
|
+
console.log(chalk3.cyan("Provider:") + " " + provider.name);
|
|
1115
|
+
console.log(chalk3.cyan("Model:") + " " + modelId);
|
|
1116
|
+
console.log(chalk3.cyan("Base URL:") + " " + provider.getBaseUrl());
|
|
1117
|
+
console.log(chalk3.cyan("Mode:") + " " + mode);
|
|
529
1118
|
console.log("");
|
|
530
1119
|
}
|
|
1120
|
+
function shouldTriggerSetup() {
|
|
1121
|
+
const status = detectSetupStatus();
|
|
1122
|
+
if (status === "first-time") {
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
if (status === "incomplete") {
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
531
1130
|
function isClaudeInstalled() {
|
|
532
1131
|
try {
|
|
533
1132
|
execSync("which claude", { stdio: "ignore" });
|
|
@@ -602,16 +1201,16 @@ function formatHealthStatus(result) {
|
|
|
602
1201
|
const name = provider?.name || result.provider;
|
|
603
1202
|
if (result.healthy) {
|
|
604
1203
|
const latency = result.latencyMs ? ` (${result.latencyMs}ms)` : "";
|
|
605
|
-
return `${
|
|
1204
|
+
return `${chalk3.green("\u2713")} ${name}${chalk3.gray(latency)}`;
|
|
606
1205
|
} else {
|
|
607
1206
|
const error = result.error?.message || "unavailable";
|
|
608
|
-
return `${
|
|
1207
|
+
return `${chalk3.red("\u2717")} ${name} ${chalk3.gray(`- ${error}`)}`;
|
|
609
1208
|
}
|
|
610
1209
|
}
|
|
611
1210
|
async function displayHealthCheck() {
|
|
612
1211
|
const skipHealthCheck = getConfig("skipHealthCheck");
|
|
613
1212
|
if (skipHealthCheck) return;
|
|
614
|
-
console.log(
|
|
1213
|
+
console.log(chalk3.gray("Checking provider availability..."));
|
|
615
1214
|
const results = await checkAllProvidersHealth();
|
|
616
1215
|
console.log("");
|
|
617
1216
|
for (const result of results) {
|
|
@@ -621,10 +1220,10 @@ async function displayHealthCheck() {
|
|
|
621
1220
|
}
|
|
622
1221
|
async function interactiveMode(skipPermissions) {
|
|
623
1222
|
console.clear();
|
|
624
|
-
|
|
1223
|
+
printHeader2("Claude Mode");
|
|
625
1224
|
await displayHealthCheck();
|
|
626
1225
|
const allProviders = getProviders();
|
|
627
|
-
const providerKey = await
|
|
1226
|
+
const providerKey = await select2({
|
|
628
1227
|
message: "Select Provider:",
|
|
629
1228
|
choices: getProviderKeys().map((key) => ({
|
|
630
1229
|
name: allProviders[key].name,
|
|
@@ -633,20 +1232,20 @@ async function interactiveMode(skipPermissions) {
|
|
|
633
1232
|
}))
|
|
634
1233
|
});
|
|
635
1234
|
const provider = getProvider(providerKey);
|
|
636
|
-
console.log(
|
|
1235
|
+
console.log(chalk3.yellow("\u2192 Selected:") + " " + provider.name);
|
|
637
1236
|
const providerModels = await getModels(providerKey);
|
|
638
1237
|
if (providerModels.length === 0) {
|
|
639
|
-
console.log(
|
|
640
|
-
console.log(
|
|
641
|
-
const modelId2 = await
|
|
1238
|
+
console.log(chalk3.yellow("No models available for this provider."));
|
|
1239
|
+
console.log(chalk3.gray("You can enter a model ID manually."));
|
|
1240
|
+
const modelId2 = await input2({
|
|
642
1241
|
message: "Enter model ID:",
|
|
643
1242
|
validate: (value) => value.length > 0 ? true : "Model ID cannot be empty"
|
|
644
1243
|
});
|
|
645
|
-
console.log(
|
|
1244
|
+
console.log(chalk3.yellow("\u2192 Model:") + " " + modelId2);
|
|
646
1245
|
await continueInteractiveMode(provider, modelId2, skipPermissions);
|
|
647
1246
|
return;
|
|
648
1247
|
}
|
|
649
|
-
const modelId = await
|
|
1248
|
+
const modelId = await select2({
|
|
650
1249
|
message: "Select Model:",
|
|
651
1250
|
choices: providerModels.map((model) => ({
|
|
652
1251
|
name: model.name,
|
|
@@ -655,11 +1254,11 @@ async function interactiveMode(skipPermissions) {
|
|
|
655
1254
|
}))
|
|
656
1255
|
});
|
|
657
1256
|
const selectedModel = providerModels.find((m) => m.id === modelId);
|
|
658
|
-
console.log(
|
|
1257
|
+
console.log(chalk3.yellow("\u2192 Selected:") + " " + selectedModel?.name + ` (${modelId})`);
|
|
659
1258
|
await continueInteractiveMode(provider, modelId, skipPermissions);
|
|
660
1259
|
}
|
|
661
1260
|
async function continueInteractiveMode(provider, modelId, skipPermissions) {
|
|
662
|
-
const mode = await
|
|
1261
|
+
const mode = await select2({
|
|
663
1262
|
message: "Select Mode:",
|
|
664
1263
|
choices: [
|
|
665
1264
|
{ name: "Terminal (interactive)", value: "terminal" },
|
|
@@ -668,13 +1267,13 @@ async function continueInteractiveMode(provider, modelId, skipPermissions) {
|
|
|
668
1267
|
});
|
|
669
1268
|
let prompt;
|
|
670
1269
|
if (mode === "headless") {
|
|
671
|
-
prompt = await
|
|
1270
|
+
prompt = await input2({
|
|
672
1271
|
message: "Enter your prompt:",
|
|
673
1272
|
validate: (value) => value.length > 0 ? true : "Prompt cannot be empty"
|
|
674
1273
|
});
|
|
675
1274
|
}
|
|
676
1275
|
if (!skipPermissions) {
|
|
677
|
-
skipPermissions = await
|
|
1276
|
+
skipPermissions = await select2({
|
|
678
1277
|
message: "Skip permission prompts when executing commands?",
|
|
679
1278
|
choices: [
|
|
680
1279
|
{ name: "No (ask for permission)", value: false },
|
|
@@ -683,19 +1282,19 @@ async function continueInteractiveMode(provider, modelId, skipPermissions) {
|
|
|
683
1282
|
});
|
|
684
1283
|
}
|
|
685
1284
|
console.log(
|
|
686
|
-
|
|
1285
|
+
chalk3.yellow("\u2192 Skip permissions:") + " " + (skipPermissions ? chalk3.red("Yes (\u26A0\uFE0F auto-approve)") : chalk3.green("No (ask)"))
|
|
687
1286
|
);
|
|
688
1287
|
const isHeadless = mode === "headless";
|
|
689
1288
|
if (isHeadless) {
|
|
690
|
-
|
|
1289
|
+
printHeader2("Executing Claude Code (Headless)");
|
|
691
1290
|
} else {
|
|
692
|
-
|
|
1291
|
+
printHeader2("Launching Claude Code (Interactive)");
|
|
693
1292
|
}
|
|
694
1293
|
printConfig(provider, modelId, isHeadless ? "Headless" : "Interactive");
|
|
695
1294
|
try {
|
|
696
1295
|
await runClaude(provider, modelId, isHeadless, skipPermissions, prompt);
|
|
697
1296
|
} catch (error) {
|
|
698
|
-
console.error(
|
|
1297
|
+
console.error(chalk3.red("Error:"), error);
|
|
699
1298
|
process.exit(1);
|
|
700
1299
|
}
|
|
701
1300
|
}
|
|
@@ -703,8 +1302,8 @@ async function quickMode(providerArg, modelArg, skipPermissions, modeArg, prompt
|
|
|
703
1302
|
const providerKey = resolveProvider(providerArg);
|
|
704
1303
|
const provider = getProvider(providerKey);
|
|
705
1304
|
if (!provider) {
|
|
706
|
-
console.error(
|
|
707
|
-
console.log(
|
|
1305
|
+
console.error(chalk3.red(`Unknown provider: ${providerArg}`));
|
|
1306
|
+
console.log(chalk3.gray("Available providers: " + getProviderKeys().join(", ")));
|
|
708
1307
|
process.exit(1);
|
|
709
1308
|
}
|
|
710
1309
|
const modelId = await resolveModel(providerKey, modelArg);
|
|
@@ -729,7 +1328,7 @@ async function quickMode(providerArg, modelArg, skipPermissions, modeArg, prompt
|
|
|
729
1328
|
}
|
|
730
1329
|
}
|
|
731
1330
|
if (isHeadless && !prompt) {
|
|
732
|
-
prompt = await
|
|
1331
|
+
prompt = await input2({
|
|
733
1332
|
message: "Enter your prompt:",
|
|
734
1333
|
validate: (value) => value.length > 0 ? true : "Prompt cannot be empty"
|
|
735
1334
|
});
|
|
@@ -738,23 +1337,23 @@ async function quickMode(providerArg, modelArg, skipPermissions, modeArg, prompt
|
|
|
738
1337
|
try {
|
|
739
1338
|
await runClaude(provider, modelId, isHeadless, skipPermissions, prompt);
|
|
740
1339
|
} catch (error) {
|
|
741
|
-
console.error(
|
|
1340
|
+
console.error(chalk3.red("Error:"), error);
|
|
742
1341
|
process.exit(1);
|
|
743
1342
|
}
|
|
744
1343
|
}
|
|
745
1344
|
async function listModels() {
|
|
746
|
-
console.log(
|
|
1345
|
+
console.log(chalk3.bold("\nAvailable Models by Provider:\n"));
|
|
747
1346
|
const allProviders = getProviders();
|
|
748
1347
|
for (const providerKey of getProviderKeys()) {
|
|
749
1348
|
const provider = allProviders[providerKey];
|
|
750
1349
|
const providerModels = await getModels(providerKey);
|
|
751
|
-
console.log(
|
|
752
|
-
console.log(
|
|
1350
|
+
console.log(chalk3.cyan(`${provider.name}:`));
|
|
1351
|
+
console.log(chalk3.gray(` ${provider.getDescription()}`));
|
|
753
1352
|
if (providerModels.length === 0) {
|
|
754
|
-
console.log(` ${
|
|
1353
|
+
console.log(` ${chalk3.gray("(no models found)")}`);
|
|
755
1354
|
} else {
|
|
756
1355
|
for (const model of providerModels) {
|
|
757
|
-
console.log(` ${
|
|
1356
|
+
console.log(` ${chalk3.green(model.shortcut.padEnd(14))} \u2192 ${model.id}`);
|
|
758
1357
|
}
|
|
759
1358
|
}
|
|
760
1359
|
console.log("");
|
|
@@ -762,14 +1361,14 @@ async function listModels() {
|
|
|
762
1361
|
}
|
|
763
1362
|
function showConfig() {
|
|
764
1363
|
const config2 = loadConfig();
|
|
765
|
-
console.log(
|
|
1364
|
+
console.log(chalk3.bold("\nCurrent Configuration:\n"));
|
|
766
1365
|
console.log(JSON.stringify(config2, null, 2));
|
|
767
1366
|
console.log("");
|
|
768
1367
|
}
|
|
769
1368
|
function initConfigCommand() {
|
|
770
1369
|
const configPath = initConfig();
|
|
771
|
-
console.log(
|
|
772
|
-
console.log(
|
|
1370
|
+
console.log(chalk3.green(`Config file created at: ${configPath}`));
|
|
1371
|
+
console.log(chalk3.gray("Edit this file to customize your settings."));
|
|
773
1372
|
}
|
|
774
1373
|
function generateBashCompletion() {
|
|
775
1374
|
const providerKeys = getProviderKeys();
|
|
@@ -897,13 +1496,13 @@ function printCompletion(shell) {
|
|
|
897
1496
|
console.log(generateFishCompletion());
|
|
898
1497
|
break;
|
|
899
1498
|
default:
|
|
900
|
-
console.error(
|
|
901
|
-
console.log(
|
|
1499
|
+
console.error(chalk3.red(`Unknown shell: ${shell}`));
|
|
1500
|
+
console.log(chalk3.gray("Supported shells: bash, zsh, fish"));
|
|
902
1501
|
process.exit(1);
|
|
903
1502
|
}
|
|
904
1503
|
}
|
|
905
1504
|
async function healthCommand() {
|
|
906
|
-
console.log(
|
|
1505
|
+
console.log(chalk3.bold("\nProvider Health Check:\n"));
|
|
907
1506
|
const results = await checkAllProvidersHealth();
|
|
908
1507
|
for (const result of results) {
|
|
909
1508
|
console.log(" " + formatHealthStatus(result));
|
|
@@ -912,40 +1511,40 @@ async function healthCommand() {
|
|
|
912
1511
|
const healthy = results.filter((r) => r.healthy).length;
|
|
913
1512
|
const total = results.length;
|
|
914
1513
|
if (healthy === total) {
|
|
915
|
-
console.log(
|
|
1514
|
+
console.log(chalk3.green(`All ${total} providers are healthy.`));
|
|
916
1515
|
} else {
|
|
917
|
-
console.log(
|
|
1516
|
+
console.log(chalk3.yellow(`${healthy}/${total} providers are healthy.`));
|
|
918
1517
|
}
|
|
919
1518
|
}
|
|
920
1519
|
program.name("claude-mode").description(
|
|
921
1520
|
`
|
|
922
|
-
${
|
|
1521
|
+
${chalk3.bold("Claude Mode")} - Launch Claude Code with different providers
|
|
923
1522
|
|
|
924
|
-
${
|
|
1523
|
+
${chalk3.bold("Providers:")}
|
|
925
1524
|
openrouter OpenRouter API
|
|
926
1525
|
ollama-cloud Ollama Cloud (OLLAMA_HOST)
|
|
927
1526
|
ollama-local Ollama Local (OLLAMA_BASE_URL_LOCAL)
|
|
928
1527
|
ollama-custom Ollama Custom (OLLAMA_BASE_URL_CUSTOM)
|
|
929
1528
|
|
|
930
|
-
${
|
|
1529
|
+
${chalk3.bold("Model shortcuts:")}
|
|
931
1530
|
OpenRouter (Premium): gpt52, gpt52-pro, gpt52-codex, opus, grok
|
|
932
1531
|
OpenRouter (Value): deepseek, zai-glm47-flash, seed16, sonnet, haiku
|
|
933
1532
|
OpenRouter (Existing): gpt120, glm47, gemini-pro, gemini-flash
|
|
934
1533
|
Ollama: Models discovered dynamically via API
|
|
935
1534
|
|
|
936
|
-
${
|
|
1535
|
+
${chalk3.bold("Mode shortcuts:")}
|
|
937
1536
|
terminal, t, interactive, i - Interactive terminal mode
|
|
938
1537
|
headless, h, prompt, p - Headless mode with prompt
|
|
939
1538
|
|
|
940
|
-
${
|
|
1539
|
+
${chalk3.bold("Options:")}
|
|
941
1540
|
-p, --prompt <prompt> Headless mode (uses defaults if provider/model not set)
|
|
942
1541
|
-d, --dangerously-skip-permissions Skip permission prompts (\u26A0\uFE0F use with caution)
|
|
943
1542
|
|
|
944
|
-
${
|
|
1543
|
+
${chalk3.bold("Default Provider/Model:")}
|
|
945
1544
|
Set in ~/.claude-mode/claude-mode.json:
|
|
946
1545
|
{ "defaultProvider": "openrouter", "defaultModel": "sonnet" }
|
|
947
1546
|
|
|
948
|
-
${
|
|
1547
|
+
${chalk3.bold("Examples:")}
|
|
949
1548
|
claude-mode Interactive menu
|
|
950
1549
|
claude-mode -p "perform a code review" Headless with default provider/model
|
|
951
1550
|
claude-mode openrouter sonnet Interactive with Claude Sonnet
|
|
@@ -978,7 +1577,24 @@ program.command("config").description("Manage configuration").argument("[action]
|
|
|
978
1577
|
program.command("completion").description("Generate shell completion scripts").argument("<shell>", "Shell type: bash, zsh, fish").action((shell) => {
|
|
979
1578
|
printCompletion(shell);
|
|
980
1579
|
});
|
|
1580
|
+
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) => {
|
|
1581
|
+
await runSetup({
|
|
1582
|
+
force: options.force,
|
|
1583
|
+
provider: options.provider,
|
|
1584
|
+
skipValidation: options.skipValidation
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
981
1587
|
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) => {
|
|
1588
|
+
if (shouldTriggerSetup()) {
|
|
1589
|
+
const shouldSetup = await confirm2({
|
|
1590
|
+
message: "No configuration found. Would you like to run setup?",
|
|
1591
|
+
default: true
|
|
1592
|
+
});
|
|
1593
|
+
if (shouldSetup) {
|
|
1594
|
+
await runSetup({ force: false });
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
982
1598
|
if (options.list) {
|
|
983
1599
|
await listModels();
|
|
984
1600
|
return;
|
|
@@ -990,14 +1606,14 @@ program.option("-d, --dangerously-skip-permissions", "Skip permission prompts wh
|
|
|
990
1606
|
const effectiveProvider = provider || defaultProvider;
|
|
991
1607
|
const effectiveModel = model || defaultModel;
|
|
992
1608
|
if (!effectiveProvider || !effectiveModel) {
|
|
993
|
-
console.error(
|
|
1609
|
+
console.error(chalk3.red("Error: Provider and model are required"));
|
|
994
1610
|
if (!effectiveProvider && !effectiveModel) {
|
|
995
|
-
console.log(
|
|
996
|
-
console.log(
|
|
1611
|
+
console.log(chalk3.gray("Set defaults in config: claude-mode config init"));
|
|
1612
|
+
console.log(chalk3.gray("Then edit ~/.claude-mode/claude-mode.json to set defaultProvider and defaultModel"));
|
|
997
1613
|
} else if (!effectiveProvider) {
|
|
998
|
-
console.log(
|
|
1614
|
+
console.log(chalk3.gray("Missing: provider (set defaultProvider in config or pass as argument)"));
|
|
999
1615
|
} else {
|
|
1000
|
-
console.log(
|
|
1616
|
+
console.log(chalk3.gray("Missing: model (set defaultModel in config or pass as argument)"));
|
|
1001
1617
|
}
|
|
1002
1618
|
process.exit(1);
|
|
1003
1619
|
}
|
|
@@ -1011,10 +1627,10 @@ program.option("-d, --dangerously-skip-permissions", "Skip permission prompts wh
|
|
|
1011
1627
|
if (defaultModel) {
|
|
1012
1628
|
await quickMode(provider, defaultModel, skipPermissions, mode, promptArg);
|
|
1013
1629
|
} else {
|
|
1014
|
-
console.error(
|
|
1015
|
-
console.log(
|
|
1016
|
-
console.log(
|
|
1017
|
-
console.log(
|
|
1630
|
+
console.error(chalk3.red("Error: Model is required when provider is specified"));
|
|
1631
|
+
console.log(chalk3.gray("Usage: claude-mode <provider> <model> [mode] [prompt]"));
|
|
1632
|
+
console.log(chalk3.gray("Or set defaultModel in ~/.claude-mode/claude-mode.json"));
|
|
1633
|
+
console.log(chalk3.gray("Run claude-mode --list to see available models"));
|
|
1018
1634
|
process.exit(1);
|
|
1019
1635
|
}
|
|
1020
1636
|
} else {
|