mirror of
https://github.com/lmstudio-ai/lms.git
synced 2025-09-19 21:40:02 +08:00
Environment support for multiple hosts or ports (#278)
* Environment support for multiple hosts or ports * Better error handling * Minor changes * Correct handling for local * Add inspect command * Address comments * Address comments more
This commit is contained in:
155
src/EnvironmentManager.ts
Normal file
155
src/EnvironmentManager.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { readFile, writeFile, mkdir, access, unlink, readdir } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
import { lmsConfigFolder } from "./lmstudioPaths.js";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { type SimpleLogger } from "@lmstudio/lms-common";
|
||||||
|
const environmentConfigSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
host: z.string(),
|
||||||
|
port: z.number().int().min(0).max(65535),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type EnvironmentConfig = z.infer<typeof environmentConfigSchema>;
|
||||||
|
|
||||||
|
export const DEFAULT_LOCAL_ENVIRONMENT_NAME = "local";
|
||||||
|
|
||||||
|
const DEFAULT_ENVIRONMENT_CONFIG: EnvironmentConfig = {
|
||||||
|
name: DEFAULT_LOCAL_ENVIRONMENT_NAME,
|
||||||
|
host: "localhost",
|
||||||
|
port: 1234,
|
||||||
|
description: "Default local environment",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EnvironmentManager {
|
||||||
|
private environmentsDir: string;
|
||||||
|
private currentEnvFile: string;
|
||||||
|
|
||||||
|
public constructor(private readonly logger: SimpleLogger) {
|
||||||
|
const configDir = lmsConfigFolder;
|
||||||
|
this.environmentsDir = join(configDir, "environments");
|
||||||
|
this.currentEnvFile = join(configDir, "current-env");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureDirExists(): Promise<void> {
|
||||||
|
await mkdir(this.environmentsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addEnvironment(config: EnvironmentConfig): Promise<void> {
|
||||||
|
await this.ensureDirExists();
|
||||||
|
const envPath = join(this.environmentsDir, `${config.name}.json`);
|
||||||
|
try {
|
||||||
|
await access(envPath);
|
||||||
|
throw new Error(`Environment ${config.name} already exists.`);
|
||||||
|
} catch {
|
||||||
|
await writeFile(envPath, JSON.stringify(config, null, 2), "utf-8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeEnvironment(name: string): Promise<void> {
|
||||||
|
const envPath = join(this.environmentsDir, `${name}.json`);
|
||||||
|
try {
|
||||||
|
await unlink(envPath);
|
||||||
|
// Check if this was the current environment
|
||||||
|
try {
|
||||||
|
const currentEnv = await readFile(this.currentEnvFile, "utf-8");
|
||||||
|
if (currentEnv === name) {
|
||||||
|
await writeFile(this.currentEnvFile, DEFAULT_LOCAL_ENVIRONMENT_NAME, "utf-8");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
||||||
|
await writeFile(this.currentEnvFile, DEFAULT_LOCAL_ENVIRONMENT_NAME, "utf-8");
|
||||||
|
} else {
|
||||||
|
// Re-throw other types of errors
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
||||||
|
throw new Error(`Environment ${name} does not exist.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to remove environment ${name}: ${(error as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setCurrentEnvironment(name: string): Promise<void> {
|
||||||
|
if (name === "local") {
|
||||||
|
// Special case for local environment
|
||||||
|
await writeFile(this.currentEnvFile, DEFAULT_LOCAL_ENVIRONMENT_NAME, "utf-8");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const envPath = join(this.environmentsDir, `${name}.json`);
|
||||||
|
try {
|
||||||
|
const data = await readFile(envPath, "utf-8");
|
||||||
|
environmentConfigSchema.parse(JSON.parse(data)); // Validate schema
|
||||||
|
await writeFile(this.currentEnvFile, name, "utf-8");
|
||||||
|
} catch {
|
||||||
|
throw new Error(`Environment ${name} does not exist.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCurrentEnvironment(): Promise<EnvironmentConfig> {
|
||||||
|
let envName: string;
|
||||||
|
|
||||||
|
// Check if LMS_ENV is set in the environment variables
|
||||||
|
// This takes precedence over the currentEnvFile
|
||||||
|
if (
|
||||||
|
process.env.LMS_ENV &&
|
||||||
|
process.env.LMS_ENV !== "undefined" &&
|
||||||
|
process.env.LMS_ENV !== "null"
|
||||||
|
) {
|
||||||
|
envName = process.env.LMS_ENV;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
envName = (await readFile(this.currentEnvFile, "utf-8")).trim();
|
||||||
|
} catch {
|
||||||
|
envName = DEFAULT_LOCAL_ENVIRONMENT_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (envName === undefined || envName === "" || envName === DEFAULT_LOCAL_ENVIRONMENT_NAME) {
|
||||||
|
return DEFAULT_ENVIRONMENT_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = await this.tryGetEnvironment(envName);
|
||||||
|
if (env === undefined) {
|
||||||
|
this.logger.warn(`Environment ${envName} not found, falling back to local.`);
|
||||||
|
await writeFile(this.currentEnvFile, DEFAULT_LOCAL_ENVIRONMENT_NAME, "utf-8");
|
||||||
|
return DEFAULT_ENVIRONMENT_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAllEnvironments(): Promise<EnvironmentConfig[]> {
|
||||||
|
await this.ensureDirExists();
|
||||||
|
const files = await readdir(this.environmentsDir);
|
||||||
|
const environments: EnvironmentConfig[] = [DEFAULT_ENVIRONMENT_CONFIG];
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith(".json")) {
|
||||||
|
try {
|
||||||
|
const data = await readFile(join(this.environmentsDir, file), "utf-8");
|
||||||
|
const parsed = environmentConfigSchema.parse(JSON.parse(data));
|
||||||
|
environments.push(parsed);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Failed to load environment from ${file}: ${(error as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return environments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async tryGetEnvironment(name: string): Promise<EnvironmentConfig | undefined> {
|
||||||
|
if (name === DEFAULT_LOCAL_ENVIRONMENT_NAME) {
|
||||||
|
return DEFAULT_ENVIRONMENT_CONFIG; // Return default local environment
|
||||||
|
}
|
||||||
|
await this.ensureDirExists();
|
||||||
|
const envPath = join(this.environmentsDir, `${name}.json`);
|
||||||
|
try {
|
||||||
|
const data = await readFile(envPath, "utf-8");
|
||||||
|
return environmentConfigSchema.parse(JSON.parse(data));
|
||||||
|
} catch {
|
||||||
|
return undefined; // Environment does not exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,13 @@
|
|||||||
import { apiServerPorts, type SimpleLogger, text } from "@lmstudio/lms-common";
|
import { apiServerPorts, type SimpleLogger, text } from "@lmstudio/lms-common";
|
||||||
import { LMStudioClient, type LMStudioClientConstructorOpts } from "@lmstudio/sdk";
|
import { LMStudioClient, type LMStudioClientConstructorOpts } from "@lmstudio/sdk";
|
||||||
import chalk from "chalk";
|
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import { option, optional, string } from "cmd-ts";
|
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import { appInstallLocationFilePath, lmsKey2Path } from "./lmstudioPaths.js";
|
import { appInstallLocationFilePath, lmsKey2Path } from "./lmstudioPaths.js";
|
||||||
import { type LogLevelArgs } from "./logLevel.js";
|
import { type LogLevelArgs } from "./logLevel.js";
|
||||||
import { checkHttpServer } from "./subcommands/server.js";
|
import { checkHttpServer } from "./subcommands/server.js";
|
||||||
import { refinedNumber } from "./types/refinedNumber.js";
|
import { DEFAULT_LOCAL_ENVIRONMENT_NAME, EnvironmentManager } from "./EnvironmentManager.js";
|
||||||
|
import { option, optional, string } from "cmd-ts";
|
||||||
interface AppInstallLocation {
|
interface AppInstallLocation {
|
||||||
path: string;
|
path: string;
|
||||||
argv: Array<string>;
|
argv: Array<string>;
|
||||||
@ -17,30 +15,20 @@ interface AppInstallLocation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createClientArgs = {
|
export const createClientArgs = {
|
||||||
host: option({
|
env: option({
|
||||||
type: optional(string),
|
type: optional(string),
|
||||||
long: "host",
|
long: "env",
|
||||||
description: text`
|
description: text`
|
||||||
If you wish to connect to a remote LM Studio instance, specify the host here. Note that, in
|
If you wish to connect to a remote LM Studio instance, specify the env here. Note that, in
|
||||||
this case, lms will connect using client identifier "lms-cli-remote-<random chars>", which
|
this case, lms will connect using client identifier "lms-cli-remote-<random chars>", which
|
||||||
will not be a privileged client, and will restrict usage of functionalities such as
|
will not be a privileged client, and will restrict usage of functionalities such as
|
||||||
"lms push".
|
"lms push". Know more about envs using lms env.
|
||||||
`,
|
|
||||||
}),
|
|
||||||
port: option({
|
|
||||||
type: optional(refinedNumber({ integer: true, min: 0, max: 65535 })),
|
|
||||||
long: "port",
|
|
||||||
description: text`
|
|
||||||
The port where LM Studio can be reached. If not provided and the host is set to "127.0.0.1"
|
|
||||||
(default), the last used port will be used; otherwise, 1234 will be used.
|
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
interface CreateClientArgs {
|
interface CreateClientArgs {
|
||||||
yes?: boolean;
|
yes?: boolean;
|
||||||
host?: string;
|
|
||||||
port?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isLocalServerAtPortLMStudioServerOrThrow(port: number) {
|
async function isLocalServerAtPortLMStudioServerOrThrow(port: number) {
|
||||||
@ -107,20 +95,22 @@ export async function createClient(
|
|||||||
args: CreateClientArgs & LogLevelArgs,
|
args: CreateClientArgs & LogLevelArgs,
|
||||||
_opts: CreateClientOpts = {},
|
_opts: CreateClientOpts = {},
|
||||||
) {
|
) {
|
||||||
let { host, port } = args;
|
const envManager = new EnvironmentManager(logger);
|
||||||
let isRemote = true;
|
let currentEnv = await envManager.getCurrentEnvironment();
|
||||||
if (host === undefined) {
|
let host = currentEnv.host;
|
||||||
isRemote = false;
|
let port = currentEnv.port;
|
||||||
host = "127.0.0.1";
|
if (args.env) {
|
||||||
} else if (host.includes("://")) {
|
// If the user specified an environment, we will override the current environment with the specified one.
|
||||||
logger.error("Host should not include the protocol.");
|
const specificedEnv = await envManager.tryGetEnvironment(args.env);
|
||||||
process.exit(1);
|
if (specificedEnv === undefined) {
|
||||||
} else if (host.includes(":")) {
|
logger.error(`Environment '${args.env}' not found`);
|
||||||
logger.error(
|
|
||||||
`Host should not include the port number. Use ${chalk.yellowBright("--port")} instead.`,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
host = specificedEnv.host;
|
||||||
|
port = specificedEnv.port;
|
||||||
|
currentEnv = specificedEnv;
|
||||||
|
}
|
||||||
|
const isRemote = currentEnv.name !== DEFAULT_LOCAL_ENVIRONMENT_NAME;
|
||||||
let auth: LMStudioClientConstructorOpts;
|
let auth: LMStudioClientConstructorOpts;
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
// If connecting to a remote server, we will use a random client identifier.
|
// If connecting to a remote server, we will use a random client identifier.
|
||||||
@ -146,7 +136,7 @@ export async function createClient(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (port === undefined && host === "127.0.0.1") {
|
if (isRemote === false) {
|
||||||
// We will now attempt to connect to the local API server.
|
// We will now attempt to connect to the local API server.
|
||||||
const localPort = await tryFindLocalAPIServer();
|
const localPort = await tryFindLocalAPIServer();
|
||||||
|
|
||||||
@ -186,11 +176,6 @@ export async function createClient(
|
|||||||
|
|
||||||
logger.error("");
|
logger.error("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (port === undefined) {
|
|
||||||
port = 1234;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`Connecting to server at ${host}:${port}`);
|
logger.debug(`Connecting to server at ${host}:${port}`);
|
||||||
if (!(await checkHttpServer(logger, port, host))) {
|
if (!(await checkHttpServer(logger, port, host))) {
|
||||||
logger.error(
|
logger.error(
|
||||||
|
@ -16,6 +16,7 @@ import { server } from "./subcommands/server.js";
|
|||||||
import { status } from "./subcommands/status.js";
|
import { status } from "./subcommands/status.js";
|
||||||
import { unload } from "./subcommands/unload.js";
|
import { unload } from "./subcommands/unload.js";
|
||||||
import { printVersion, version } from "./subcommands/version.js";
|
import { printVersion, version } from "./subcommands/version.js";
|
||||||
|
import { env } from "./subcommands/env.js";
|
||||||
|
|
||||||
if (process.argv.length === 2) {
|
if (process.argv.length === 2) {
|
||||||
printVersion();
|
printVersion();
|
||||||
@ -44,6 +45,7 @@ const cli = subcommands({
|
|||||||
flags: flagsCommand,
|
flags: flagsCommand,
|
||||||
bootstrap,
|
bootstrap,
|
||||||
version,
|
version,
|
||||||
|
env,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { findLMStudioHome } from "@lmstudio/lms-common-server";
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
const lmstudioHome = findLMStudioHome();
|
const lmstudioHome = findLMStudioHome();
|
||||||
|
export const lmsConfigFolder = join(lmstudioHome, "lms"); //TODO: Temporary path
|
||||||
export const pluginsFolderPath = join(lmstudioHome, "extensions", "plugins");
|
export const pluginsFolderPath = join(lmstudioHome, "extensions", "plugins");
|
||||||
export const lmsKey2Path = join(lmstudioHome, ".internal", "lms-key-2");
|
export const lmsKey2Path = join(lmstudioHome, ".internal", "lms-key-2");
|
||||||
export const cliPrefPath = join(lmstudioHome, ".internal", "cli-pref.json");
|
export const cliPrefPath = join(lmstudioHome, ".internal", "cli-pref.json");
|
||||||
|
206
src/subcommands/env.ts
Normal file
206
src/subcommands/env.ts
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import { text } from "@lmstudio/lms-common";
|
||||||
|
import { command, option, optional, positional, string, subcommands } from "cmd-ts";
|
||||||
|
import { EnvironmentManager } from "../EnvironmentManager.js";
|
||||||
|
import { createLogger, logLevelArgs } from "../logLevel.js";
|
||||||
|
import { refinedNumber } from "../types/refinedNumber.js";
|
||||||
|
|
||||||
|
const addEnvCommand = command({
|
||||||
|
name: "add",
|
||||||
|
description: "Add a new environment",
|
||||||
|
args: {
|
||||||
|
name: positional({
|
||||||
|
type: string,
|
||||||
|
displayName: "name",
|
||||||
|
description: "Environment name",
|
||||||
|
}),
|
||||||
|
host: option({
|
||||||
|
type: string,
|
||||||
|
long: "host",
|
||||||
|
description: "Host address",
|
||||||
|
}),
|
||||||
|
port: option({
|
||||||
|
type: refinedNumber({ integer: true, min: 0, max: 65535 }),
|
||||||
|
long: "port",
|
||||||
|
description: "Port number",
|
||||||
|
}),
|
||||||
|
description: option({
|
||||||
|
type: optional(string),
|
||||||
|
long: "description",
|
||||||
|
description: "Environment description",
|
||||||
|
}),
|
||||||
|
...logLevelArgs,
|
||||||
|
},
|
||||||
|
handler: async ({ name, host, port, description, ...logArgs }) => {
|
||||||
|
const logger = createLogger(logArgs);
|
||||||
|
const envManager = new EnvironmentManager(logger);
|
||||||
|
try {
|
||||||
|
await envManager.addEnvironment({
|
||||||
|
name,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
logger.info(`Environment '${name}' added successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to add environment: ${(error as Error).message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeEnvCommand = command({
|
||||||
|
name: "remove",
|
||||||
|
description: "Remove an environment",
|
||||||
|
args: {
|
||||||
|
name: positional({
|
||||||
|
type: string,
|
||||||
|
displayName: "name",
|
||||||
|
description: "Environment name to remove",
|
||||||
|
}),
|
||||||
|
...logLevelArgs,
|
||||||
|
},
|
||||||
|
handler: async ({ name, ...logArgs }) => {
|
||||||
|
const logger = createLogger(logArgs);
|
||||||
|
const envManager = new EnvironmentManager(logger);
|
||||||
|
try {
|
||||||
|
await envManager.removeEnvironment(name);
|
||||||
|
logger.info(`Environment '${name}' removed successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to remove environment: ${(error as Error).message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const listEnvCommand = command({
|
||||||
|
name: "ls",
|
||||||
|
description: "List all environments",
|
||||||
|
args: {
|
||||||
|
...logLevelArgs,
|
||||||
|
},
|
||||||
|
handler: async logArgs => {
|
||||||
|
const logger = createLogger(logArgs);
|
||||||
|
const envManager = new EnvironmentManager(logger);
|
||||||
|
try {
|
||||||
|
const environments = await envManager.getAllEnvironments();
|
||||||
|
const current = await envManager.getCurrentEnvironment();
|
||||||
|
|
||||||
|
if (environments.length === 0) {
|
||||||
|
logger.info("No environments found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Available environments:");
|
||||||
|
for (const env of environments) {
|
||||||
|
const isCurrent = env.name === current.name;
|
||||||
|
const marker = isCurrent ? "* " : " ";
|
||||||
|
const desc = env.description ? ` - ${env.description}` : "";
|
||||||
|
logger.info(`${marker}${env.name} (${env.host}:${env.port})${desc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show default local environment if not in list
|
||||||
|
const hasLocal = environments.some(env => env.name === "local");
|
||||||
|
if (!hasLocal) {
|
||||||
|
const marker = current.name === "local" ? "* " : " ";
|
||||||
|
logger.info(`${marker}local (localhost:1234) - Default local environment`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to list environments: ${(error as Error).message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const useEnvCommand = command({
|
||||||
|
name: "use",
|
||||||
|
description: "Switch to an environment",
|
||||||
|
args: {
|
||||||
|
name: positional({
|
||||||
|
type: string,
|
||||||
|
displayName: "name",
|
||||||
|
description: "Environment name to switch to",
|
||||||
|
}),
|
||||||
|
...logLevelArgs,
|
||||||
|
},
|
||||||
|
handler: async ({ name, ...logArgs }) => {
|
||||||
|
const logger = createLogger(logArgs);
|
||||||
|
const envManager = new EnvironmentManager(logger);
|
||||||
|
try {
|
||||||
|
await envManager.setCurrentEnvironment(name);
|
||||||
|
logger.info(`Switched to environment '${name}'`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to switch environment: ${(error as Error).message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentEnvCommand = command({
|
||||||
|
name: "current",
|
||||||
|
description: "Show current environment",
|
||||||
|
args: {
|
||||||
|
...logLevelArgs,
|
||||||
|
},
|
||||||
|
handler: async logArgs => {
|
||||||
|
const logger = createLogger(logArgs);
|
||||||
|
const envManager = new EnvironmentManager(logger);
|
||||||
|
try {
|
||||||
|
const current = await envManager.getCurrentEnvironment();
|
||||||
|
const desc = current.description ? ` - ${current.description}` : "";
|
||||||
|
logger.info(`Current environment: ${current.name} (${current.host}:${current.port})${desc}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to get current environment: ${(error as Error).message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const inspectEnvCommand = command({
|
||||||
|
name: "inspect",
|
||||||
|
description: "Show detailed information about an environment",
|
||||||
|
args: {
|
||||||
|
name: positional({
|
||||||
|
type: string,
|
||||||
|
displayName: "name",
|
||||||
|
description: "Environment name to inspect",
|
||||||
|
}),
|
||||||
|
...logLevelArgs,
|
||||||
|
},
|
||||||
|
handler: async ({ name, ...logArgs }) => {
|
||||||
|
const logger = createLogger(logArgs);
|
||||||
|
const envManager = new EnvironmentManager(logger);
|
||||||
|
try {
|
||||||
|
const env = await envManager.tryGetEnvironment(name);
|
||||||
|
if (!env) {
|
||||||
|
logger.error(`Environment '${name}' not found`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Environment: ${env.name}`);
|
||||||
|
logger.info(`Host: ${env.host}`);
|
||||||
|
logger.info(`Port: ${env.port}`);
|
||||||
|
if (env.description !== undefined) {
|
||||||
|
logger.info(`Description: ${env.description}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to inspect environment: ${(error as Error).message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const env = subcommands({
|
||||||
|
name: "env",
|
||||||
|
description: text`
|
||||||
|
Manage LM Studio environments. Environments allow you to switch between different
|
||||||
|
LM Studio instances (local or remote) easily.
|
||||||
|
`,
|
||||||
|
cmds: {
|
||||||
|
add: addEnvCommand,
|
||||||
|
remove: removeEnvCommand,
|
||||||
|
ls: listEnvCommand,
|
||||||
|
use: useEnvCommand,
|
||||||
|
current: currentEnvCommand,
|
||||||
|
inspect: inspectEnvCommand,
|
||||||
|
},
|
||||||
|
});
|
@ -2,36 +2,24 @@ import { text } from "@lmstudio/lms-common";
|
|||||||
import boxen from "boxen";
|
import boxen from "boxen";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { command } from "cmd-ts";
|
import { command } from "cmd-ts";
|
||||||
import { createClient, createClientArgs } from "../createClient.js";
|
import { createClient } from "../createClient.js";
|
||||||
|
import { EnvironmentManager } from "../EnvironmentManager.js";
|
||||||
import { formatSizeBytesWithColor1000 } from "../formatSizeBytes1000.js";
|
import { formatSizeBytesWithColor1000 } from "../formatSizeBytes1000.js";
|
||||||
import { createLogger, logLevelArgs } from "../logLevel.js";
|
import { createLogger, logLevelArgs } from "../logLevel.js";
|
||||||
import { checkHttpServer, getServerConfig } from "./server.js";
|
import { checkHttpServer } from "./server.js";
|
||||||
|
|
||||||
export const status = command({
|
export const status = command({
|
||||||
name: "status",
|
name: "status",
|
||||||
description: "Prints the status of LM Studio",
|
description: "Prints the status of LM Studio",
|
||||||
args: {
|
args: {
|
||||||
...logLevelArgs,
|
...logLevelArgs,
|
||||||
...createClientArgs,
|
|
||||||
},
|
},
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
const logger = createLogger(args);
|
const logger = createLogger(args);
|
||||||
let { host, port } = args;
|
const envManager = new EnvironmentManager(logger);
|
||||||
if (host === undefined) {
|
const currentEnv = await envManager.getCurrentEnvironment();
|
||||||
host = "127.0.0.1";
|
const host = currentEnv.host;
|
||||||
}
|
const port = currentEnv.port;
|
||||||
if (port === undefined) {
|
|
||||||
if (host === "127.0.0.1") {
|
|
||||||
try {
|
|
||||||
port = (await getServerConfig(logger)).port;
|
|
||||||
} catch (e) {
|
|
||||||
logger.debug(`Failed to read last status`, e);
|
|
||||||
port = 1234;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
port = 1234;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const running = await checkHttpServer(logger, port, host);
|
const running = await checkHttpServer(logger, port, host);
|
||||||
let content = "";
|
let content = "";
|
||||||
if (running) {
|
if (running) {
|
||||||
|
Reference in New Issue
Block a user