swiftroutercli 1.0.0 → 1.1.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.
Binary file
package/dist/config.js CHANGED
@@ -1,18 +1,28 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import os from "os";
4
- const CONFIG_FILE = path.join(os.homedir(), ".swiftrouter-cli", "config.json");
3
+ import { CONFIG_FILE, ENV_API_KEY, ENV_BASE_URL } from "./constants.js";
5
4
  export function loadConfig() {
5
+ // 1. Check for Environment Variables overriding everything else
6
+ const envApiKey = process.env[ENV_API_KEY];
7
+ const envBaseUrl = process.env[ENV_BASE_URL];
8
+ let fileConfig = null;
9
+ // 2. Load from file if exists
6
10
  try {
7
- if (!fs.existsSync(CONFIG_FILE)) {
8
- return null;
11
+ if (fs.existsSync(CONFIG_FILE)) {
12
+ const data = fs.readFileSync(CONFIG_FILE, "utf-8");
13
+ fileConfig = JSON.parse(data);
9
14
  }
10
- const data = fs.readFileSync(CONFIG_FILE, "utf-8");
11
- return JSON.parse(data);
12
15
  }
13
16
  catch (error) {
14
- return null;
17
+ // ignore parse errors and fallback
15
18
  }
19
+ // 3. Merge Env Vars with File Config (Env wins)
20
+ const finalApiKey = envApiKey || fileConfig?.apiKey;
21
+ const finalBaseUrl = envBaseUrl || fileConfig?.baseUrl;
22
+ if (finalApiKey && finalBaseUrl) {
23
+ return { apiKey: finalApiKey, baseUrl: finalBaseUrl };
24
+ }
25
+ return null;
16
26
  }
17
27
  export function saveConfig(config) {
18
28
  const dir = path.dirname(CONFIG_FILE);
@@ -0,0 +1,11 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ // Application Defaults
4
+ export const DEFAULT_BASE_URL = "http://localhost:8080";
5
+ export const DEFAULT_MODEL = "gpt-4o-mini";
6
+ // System Paths
7
+ export const CONFIG_DIR = path.join(os.homedir(), ".swiftrouter-cli");
8
+ export const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
9
+ // Environment Variable Keys
10
+ export const ENV_API_KEY = "SWIFTROUTER_API_KEY";
11
+ export const ENV_BASE_URL = "SWIFTROUTER_BASE_URL";
package/dist/index.js CHANGED
@@ -1,36 +1,54 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import * as readline from "readline/promises";
4
+ import chalk from "chalk";
5
+ import ora from "ora";
4
6
  import { loadConfig, saveConfig } from "./config.js";
5
7
  import { fetchModels } from "./api/client.js";
6
8
  import { startChat } from "./ui/Chat.js";
9
+ import { DEFAULT_BASE_URL, DEFAULT_MODEL, CONFIG_FILE } from "./constants.js";
10
+ import terminalImage from "terminal-image";
11
+ import { fileURLToPath } from "url";
12
+ import path from "path";
13
+ import fs from "fs";
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
7
16
  const program = new Command();
8
17
  async function ensureConfig() {
9
18
  const existingConfig = loadConfig();
10
19
  if (existingConfig && existingConfig.apiKey && existingConfig.baseUrl) {
11
20
  return existingConfig;
12
21
  }
13
- console.log("🌊 Welcome to SwiftRouterCLI!");
14
- console.log("It looks like this is your first time. Let's get you set up.\n");
22
+ try {
23
+ const logoPath = path.join(__dirname, "..", "assets", "logo.png");
24
+ if (fs.existsSync(logoPath)) {
25
+ console.log(await terminalImage.file(logoPath, { height: "25%" }));
26
+ }
27
+ }
28
+ catch (e) {
29
+ // Ignore if logo cannot be rendered
30
+ }
31
+ console.log(chalk.cyan.bold("\n🌊 Welcome to SwiftRouterCLI!"));
32
+ console.log(chalk.gray("It looks like this is your first time. Let's get you set up.\n"));
15
33
  const rl = readline.createInterface({
16
34
  input: process.stdin,
17
35
  output: process.stdout,
18
36
  });
19
- const apiKey = await rl.question("Please enter your SwiftRouter API Key: ");
20
- const baseUrlInput = await rl.question("Please enter your SwiftRouter Base URL (default: http://localhost:8080): ");
37
+ const apiKey = await rl.question(chalk.bold("Please enter your SwiftRouter API Key: "));
38
+ const baseUrlInput = await rl.question(chalk.bold(`Please enter your SwiftRouter Base URL (default: ${DEFAULT_BASE_URL}): `));
21
39
  rl.close();
22
40
  const config = {
23
41
  apiKey: apiKey.trim(),
24
- baseUrl: baseUrlInput.trim() || "http://localhost:8080",
42
+ baseUrl: baseUrlInput.trim() || DEFAULT_BASE_URL,
25
43
  };
26
44
  saveConfig(config);
27
- console.log("\nāœ… Configuration saved securely! Starting your session...\n");
45
+ console.log(chalk.green("\nāœ… Configuration saved securely! Starting your session...\n"));
28
46
  return config;
29
47
  }
30
48
  program
31
49
  .name("swiftrouter")
32
50
  .description("CLI for SwiftRouter AI Gateway")
33
- .version("1.0.0");
51
+ .version("1.0.1");
34
52
  program
35
53
  .command("config")
36
54
  .description("Manually configure the CLI with your SwiftRouter API Key and Base URL")
@@ -43,41 +61,83 @@ program
43
61
  if (options.setBaseUrl)
44
62
  config.baseUrl = options.setBaseUrl;
45
63
  if (!config.apiKey || !config.baseUrl) {
46
- console.error("Please provide both an API Key and a Base URL.");
47
- console.log("Usage: swiftrouter config --set-api-key <KEY> --set-base-url <URL>");
64
+ console.error(chalk.red("Please provide both an API Key and a Base URL."));
65
+ console.log(chalk.gray("Usage: swiftrouter config --set-api-key <KEY> --set-base-url <URL>"));
48
66
  process.exit(1);
49
67
  }
50
68
  saveConfig(config);
51
- console.log("āœ… Configuration saved successfully!");
69
+ console.log(chalk.green("āœ… Configuration saved successfully!"));
52
70
  });
53
71
  program
54
72
  .command("models")
55
73
  .description("List available models from SwiftRouter")
56
74
  .action(async () => {
57
75
  const config = await ensureConfig();
76
+ const spinner = ora("Fetching models from SwiftRouter...").start();
58
77
  try {
59
- console.log("Fetching models...");
60
78
  const models = await fetchModels(config);
61
- console.log(`\nAvailable Models (${models.length}):`);
62
- models.forEach((m) => console.log(` - ${m.id}`));
79
+ spinner.succeed(`Found ${models.length} models!`);
80
+ console.log(chalk.dim("\nAvailable Models:"));
81
+ models.forEach((m) => console.log(chalk.cyan(` - ${m.id}`)));
63
82
  }
64
83
  catch (e) {
65
- console.error("Failed to fetch models:", e.message);
84
+ spinner.fail("Failed to fetch models.");
85
+ if (e.message.includes("fetch failed") || e.message.includes("ECONNREFUSED")) {
86
+ console.error(chalk.red.bold(`\nāŒ Error: Cannot connect to SwiftRouter backend at ${config.baseUrl}`));
87
+ console.error(chalk.dim("Please ensure your SwiftRouter server is currently running."));
88
+ }
89
+ else {
90
+ console.error(chalk.red(e.message));
91
+ }
66
92
  }
67
93
  });
68
94
  program
69
95
  .command("chat")
70
96
  .description("Start an interactive chat session")
71
97
  .argument("[prompt]", "Initial prompt to start the chat")
72
- .option("-m, --model <model>", "Model to use", "gpt-4o-mini")
98
+ .option("-m, --model <model>", "Model to use", DEFAULT_MODEL)
73
99
  .action(async (prompt, options) => {
74
100
  const config = await ensureConfig();
75
101
  if (!prompt) {
76
- console.log("Starting empty chat session...");
77
102
  startChat(config, options.model, "");
78
103
  }
79
104
  else {
80
105
  startChat(config, options.model, prompt);
81
106
  }
82
107
  });
108
+ program
109
+ .command("status")
110
+ .description("Check the current configuration and connection status")
111
+ .action(async () => {
112
+ const config = loadConfig();
113
+ if (!config || !config.apiKey || !config.baseUrl) {
114
+ console.log(chalk.yellow("āš ļø SwiftRouterCLI is not authenticated."));
115
+ console.log(`Run ${chalk.cyan("swiftrouter config")} or any command to setup.`);
116
+ return;
117
+ }
118
+ console.log(chalk.bold("SwiftRouterCLI Status:"));
119
+ console.log(chalk.green("āœ… Authenticated"));
120
+ console.log(`${chalk.gray("Base URL:")} ${config.baseUrl}`);
121
+ console.log(`${chalk.gray("API Key:")} sk-...${config.apiKey.slice(-4)}`);
122
+ const spinner = ora("\nPinging SwiftRouter Server...").start();
123
+ try {
124
+ const models = await fetchModels(config);
125
+ spinner.succeed(chalk.green(`Connected! Found ${models.length} active models.`));
126
+ }
127
+ catch (e) {
128
+ spinner.fail(chalk.red("Failed to reach server. Are you sure it's running?"));
129
+ }
130
+ });
131
+ program
132
+ .command("logout")
133
+ .description("Clear your local SwiftRouter configuration securely")
134
+ .action(() => {
135
+ if (fs.existsSync(CONFIG_FILE)) {
136
+ fs.unlinkSync(CONFIG_FILE);
137
+ console.log(chalk.green("āœ… Successfully logged out. Configuration deleted."));
138
+ }
139
+ else {
140
+ console.log(chalk.gray("You were not logged in."));
141
+ }
142
+ });
83
143
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swiftroutercli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,11 +16,14 @@
16
16
  "author": "",
17
17
  "license": "ISC",
18
18
  "dependencies": {
19
+ "chalk": "^4.1.2",
19
20
  "commander": "^14.0.3",
20
21
  "dotenv": "^17.3.1",
21
22
  "eventsource-parser": "^3.0.6",
22
23
  "ink": "^6.8.0",
24
+ "ora": "^5.4.1",
23
25
  "react": "^19.2.4",
26
+ "terminal-image": "^4.2.0",
24
27
  "undici": "^7.22.0",
25
28
  "zod": "^4.3.6"
26
29
  },
package/src/config.ts CHANGED
@@ -1,24 +1,38 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import os from "os";
3
+ import { CONFIG_FILE, ENV_API_KEY, ENV_BASE_URL } from "./constants.js";
4
4
 
5
5
  export interface Config {
6
6
  apiKey: string;
7
7
  baseUrl: string;
8
8
  }
9
9
 
10
- const CONFIG_FILE = path.join(os.homedir(), ".swiftrouter-cli", "config.json");
11
-
12
10
  export function loadConfig(): Config | null {
11
+ // 1. Check for Environment Variables overriding everything else
12
+ const envApiKey = process.env[ENV_API_KEY];
13
+ const envBaseUrl = process.env[ENV_BASE_URL];
14
+
15
+ let fileConfig: Config | null = null;
16
+
17
+ // 2. Load from file if exists
13
18
  try {
14
- if (!fs.existsSync(CONFIG_FILE)) {
15
- return null;
19
+ if (fs.existsSync(CONFIG_FILE)) {
20
+ const data = fs.readFileSync(CONFIG_FILE, "utf-8");
21
+ fileConfig = JSON.parse(data) as Config;
16
22
  }
17
- const data = fs.readFileSync(CONFIG_FILE, "utf-8");
18
- return JSON.parse(data) as Config;
19
23
  } catch (error) {
20
- return null;
24
+ // ignore parse errors and fallback
21
25
  }
26
+
27
+ // 3. Merge Env Vars with File Config (Env wins)
28
+ const finalApiKey = envApiKey || fileConfig?.apiKey;
29
+ const finalBaseUrl = envBaseUrl || fileConfig?.baseUrl;
30
+
31
+ if (finalApiKey && finalBaseUrl) {
32
+ return { apiKey: finalApiKey, baseUrl: finalBaseUrl };
33
+ }
34
+
35
+ return null;
22
36
  }
23
37
 
24
38
  export function saveConfig(config: Config) {
@@ -0,0 +1,14 @@
1
+ import os from "os";
2
+ import path from "path";
3
+
4
+ // Application Defaults
5
+ export const DEFAULT_BASE_URL = "http://localhost:8080";
6
+ export const DEFAULT_MODEL = "gpt-4o-mini";
7
+
8
+ // System Paths
9
+ export const CONFIG_DIR = path.join(os.homedir(), ".swiftrouter-cli");
10
+ export const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
11
+
12
+ // Environment Variable Keys
13
+ export const ENV_API_KEY = "SWIFTROUTER_API_KEY";
14
+ export const ENV_BASE_URL = "SWIFTROUTER_BASE_URL";
package/src/index.ts CHANGED
@@ -1,9 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import * as readline from "readline/promises";
4
+ import chalk from "chalk";
5
+ import ora from "ora";
4
6
  import { loadConfig, saveConfig, Config } from "./config.js";
5
7
  import { fetchModels } from "./api/client.js";
6
8
  import { startChat } from "./ui/Chat.js";
9
+ import { DEFAULT_BASE_URL, DEFAULT_MODEL, CONFIG_FILE } from "./constants.js";
10
+ import terminalImage from "terminal-image";
11
+ import { fileURLToPath } from "url";
12
+ import path from "path";
13
+ import fs from "fs";
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
7
17
 
8
18
  const program = new Command();
9
19
 
@@ -13,28 +23,37 @@ async function ensureConfig(): Promise<Config> {
13
23
  return existingConfig;
14
24
  }
15
25
 
16
- console.log("🌊 Welcome to SwiftRouterCLI!");
17
- console.log("It looks like this is your first time. Let's get you set up.\n");
26
+ try {
27
+ const logoPath = path.join(__dirname, "..", "assets", "logo.png");
28
+ if (fs.existsSync(logoPath)) {
29
+ console.log(await terminalImage.file(logoPath, { height: "25%" }));
30
+ }
31
+ } catch (e) {
32
+ // Ignore if logo cannot be rendered
33
+ }
34
+
35
+ console.log(chalk.cyan.bold("\n🌊 Welcome to SwiftRouterCLI!"));
36
+ console.log(chalk.gray("It looks like this is your first time. Let's get you set up.\n"));
18
37
 
19
38
  const rl = readline.createInterface({
20
39
  input: process.stdin,
21
40
  output: process.stdout,
22
41
  });
23
42
 
24
- const apiKey = await rl.question("Please enter your SwiftRouter API Key: ");
43
+ const apiKey = await rl.question(chalk.bold("Please enter your SwiftRouter API Key: "));
25
44
  const baseUrlInput = await rl.question(
26
- "Please enter your SwiftRouter Base URL (default: http://localhost:8080): "
45
+ chalk.bold(`Please enter your SwiftRouter Base URL (default: ${DEFAULT_BASE_URL}): `)
27
46
  );
28
47
 
29
48
  rl.close();
30
49
 
31
50
  const config: Config = {
32
51
  apiKey: apiKey.trim(),
33
- baseUrl: baseUrlInput.trim() || "http://localhost:8080",
52
+ baseUrl: baseUrlInput.trim() || DEFAULT_BASE_URL,
34
53
  };
35
54
 
36
55
  saveConfig(config);
37
- console.log("\nāœ… Configuration saved securely! Starting your session...\n");
56
+ console.log(chalk.green("\nāœ… Configuration saved securely! Starting your session...\n"));
38
57
 
39
58
  return config;
40
59
  }
@@ -42,7 +61,7 @@ async function ensureConfig(): Promise<Config> {
42
61
  program
43
62
  .name("swiftrouter")
44
63
  .description("CLI for SwiftRouter AI Gateway")
45
- .version("1.0.0");
64
+ .version("1.0.1");
46
65
 
47
66
  program
48
67
  .command("config")
@@ -56,13 +75,13 @@ program
56
75
  if (options.setBaseUrl) config.baseUrl = options.setBaseUrl;
57
76
 
58
77
  if (!config.apiKey || !config.baseUrl) {
59
- console.error("Please provide both an API Key and a Base URL.");
60
- console.log("Usage: swiftrouter config --set-api-key <KEY> --set-base-url <URL>");
78
+ console.error(chalk.red("Please provide both an API Key and a Base URL."));
79
+ console.log(chalk.gray("Usage: swiftrouter config --set-api-key <KEY> --set-base-url <URL>"));
61
80
  process.exit(1);
62
81
  }
63
82
 
64
83
  saveConfig(config);
65
- console.log("āœ… Configuration saved successfully!");
84
+ console.log(chalk.green("āœ… Configuration saved successfully!"));
66
85
  });
67
86
 
68
87
  program
@@ -70,14 +89,21 @@ program
70
89
  .description("List available models from SwiftRouter")
71
90
  .action(async () => {
72
91
  const config = await ensureConfig();
92
+ const spinner = ora("Fetching models from SwiftRouter...").start();
73
93
 
74
94
  try {
75
- console.log("Fetching models...");
76
95
  const models = await fetchModels(config);
77
- console.log(`\nAvailable Models (${models.length}):`);
78
- models.forEach((m: any) => console.log(` - ${m.id}`));
96
+ spinner.succeed(`Found ${models.length} models!`);
97
+ console.log(chalk.dim("\nAvailable Models:"));
98
+ models.forEach((m: any) => console.log(chalk.cyan(` - ${m.id}`)));
79
99
  } catch (e: any) {
80
- console.error("Failed to fetch models:", e.message);
100
+ spinner.fail("Failed to fetch models.");
101
+ if (e.message.includes("fetch failed") || e.message.includes("ECONNREFUSED")) {
102
+ console.error(chalk.red.bold(`\nāŒ Error: Cannot connect to SwiftRouter backend at ${config.baseUrl}`));
103
+ console.error(chalk.dim("Please ensure your SwiftRouter server is currently running."));
104
+ } else {
105
+ console.error(chalk.red(e.message));
106
+ }
81
107
  }
82
108
  });
83
109
 
@@ -85,16 +111,52 @@ program
85
111
  .command("chat")
86
112
  .description("Start an interactive chat session")
87
113
  .argument("[prompt]", "Initial prompt to start the chat")
88
- .option("-m, --model <model>", "Model to use", "gpt-4o-mini")
114
+ .option("-m, --model <model>", "Model to use", DEFAULT_MODEL)
89
115
  .action(async (prompt, options) => {
90
116
  const config = await ensureConfig();
91
117
 
92
118
  if (!prompt) {
93
- console.log("Starting empty chat session...");
94
119
  startChat(config, options.model, "");
95
120
  } else {
96
121
  startChat(config, options.model, prompt);
97
122
  }
98
123
  });
99
124
 
125
+ program
126
+ .command("status")
127
+ .description("Check the current configuration and connection status")
128
+ .action(async () => {
129
+ const config = loadConfig();
130
+ if (!config || !config.apiKey || !config.baseUrl) {
131
+ console.log(chalk.yellow("āš ļø SwiftRouterCLI is not authenticated."));
132
+ console.log(`Run ${chalk.cyan("swiftrouter config")} or any command to setup.`);
133
+ return;
134
+ }
135
+
136
+ console.log(chalk.bold("SwiftRouterCLI Status:"));
137
+ console.log(chalk.green("āœ… Authenticated"));
138
+ console.log(`${chalk.gray("Base URL:")} ${config.baseUrl}`);
139
+ console.log(`${chalk.gray("API Key:")} sk-...${config.apiKey.slice(-4)}`);
140
+
141
+ const spinner = ora("\nPinging SwiftRouter Server...").start();
142
+ try {
143
+ const models = await fetchModels(config);
144
+ spinner.succeed(chalk.green(`Connected! Found ${models.length} active models.`));
145
+ } catch (e) {
146
+ spinner.fail(chalk.red("Failed to reach server. Are you sure it's running?"));
147
+ }
148
+ });
149
+
150
+ program
151
+ .command("logout")
152
+ .description("Clear your local SwiftRouter configuration securely")
153
+ .action(() => {
154
+ if (fs.existsSync(CONFIG_FILE)) {
155
+ fs.unlinkSync(CONFIG_FILE);
156
+ console.log(chalk.green("āœ… Successfully logged out. Configuration deleted."));
157
+ } else {
158
+ console.log(chalk.gray("You were not logged in."));
159
+ }
160
+ });
161
+
100
162
  program.parse();