mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-01-30 06:12:06 +00:00
fix statusline error
This commit is contained in:
@@ -12,7 +12,7 @@ import { activateCommand } from "./utils/activateCommand";
|
|||||||
import { readConfigFile } from "./utils";
|
import { readConfigFile } from "./utils";
|
||||||
import { version } from "../package.json";
|
import { version } from "../package.json";
|
||||||
import { spawn, exec } from "child_process";
|
import { spawn, exec } from "child_process";
|
||||||
import {PID_FILE, readPresetFile, REFERENCE_COUNT_FILE} from "@CCR/shared";
|
import {getPresetDir, loadConfigFromManifest, PID_FILE, readPresetFile, REFERENCE_COUNT_FILE} from "@CCR/shared";
|
||||||
import fs, { existsSync, readFileSync } from "fs";
|
import fs, { existsSync, readFileSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { parseStatusLineData, StatusLineInput } from "./utils/statusline";
|
import { parseStatusLineData, StatusLineInput } from "./utils/statusline";
|
||||||
@@ -81,7 +81,7 @@ async function waitForService(
|
|||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
while (Date.now() - startTime < timeout) {
|
while (Date.now() - startTime < timeout) {
|
||||||
const isRunning = await isServiceRunning()
|
const isRunning = isServiceRunning()
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
// Wait for an additional short period to ensure service is fully ready
|
// Wait for an additional short period to ensure service is fully ready
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
@@ -93,43 +93,46 @@ async function waitForService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const isRunning = await isServiceRunning()
|
const isRunning = isServiceRunning()
|
||||||
|
|
||||||
// If command is not a known command, check if it's a preset
|
// If command is not a known command, check if it's a preset
|
||||||
if (command && !KNOWN_COMMANDS.includes(command)) {
|
if (command && !KNOWN_COMMANDS.includes(command)) {
|
||||||
const presetData: any = await readPresetFile(command);
|
const manifest = await readPresetFile(command);
|
||||||
|
|
||||||
if (presetData) {
|
if (manifest) {
|
||||||
// This is a preset, execute code command
|
// This is a preset, load its configuration
|
||||||
|
const presetDir = getPresetDir(command);
|
||||||
|
const config = loadConfigFromManifest(manifest, presetDir);
|
||||||
|
|
||||||
|
// Execute code command
|
||||||
const codeArgs = process.argv.slice(3); // Get remaining arguments
|
const codeArgs = process.argv.slice(3); // Get remaining arguments
|
||||||
|
|
||||||
// Check noServer configuration
|
// Check noServer configuration
|
||||||
const shouldStartServer = presetData.noServer !== true;
|
const shouldStartServer = config.noServer !== true;
|
||||||
|
|
||||||
// Build environment variable overrides
|
// Build environment variable overrides
|
||||||
let envOverrides: Record<string, string> | undefined;
|
let envOverrides: Record<string, string> = {};
|
||||||
|
|
||||||
// Handle provider configuration (supports both old and new formats)
|
// Handle provider configuration (supports both old and new formats)
|
||||||
let provider: any = null;
|
let provider: any = null;
|
||||||
|
|
||||||
// Old format: presetData.provider is the provider name
|
// Old format: config.provider is the provider name
|
||||||
if (presetData.provider && typeof presetData.provider === 'string') {
|
if (config.provider && typeof config.provider === 'string') {
|
||||||
const config = await readConfigFile();
|
const globalConfig = await readConfigFile();
|
||||||
provider = config.Providers?.find((p: any) => p.name === presetData.provider);
|
provider = globalConfig.Providers?.find((p: any) => p.name === config.provider);
|
||||||
}
|
}
|
||||||
// New format: presetData.Providers is an array of providers
|
// New format: config.Providers is an array of providers
|
||||||
else if (presetData.Providers && presetData.Providers.length > 0) {
|
else if (config.Providers && config.Providers.length > 0) {
|
||||||
provider = presetData.Providers[0];
|
provider = config.Providers[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If noServer is not true, use local server baseurl
|
// If noServer is not true, use local server baseurl
|
||||||
if (shouldStartServer) {
|
if (shouldStartServer) {
|
||||||
const config = await readConfigFile();
|
const globalConfig = await readConfigFile();
|
||||||
const port = config.PORT || 3456;
|
const port = globalConfig.PORT || 3456;
|
||||||
const presetName = command;
|
|
||||||
envOverrides = {
|
envOverrides = {
|
||||||
...envOverrides,
|
...envOverrides,
|
||||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}/preset/${presetName}`,
|
ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}/preset/${command}`,
|
||||||
};
|
};
|
||||||
} else if (provider) {
|
} else if (provider) {
|
||||||
// Handle api_base_url, remove /v1/messages suffix
|
// Handle api_base_url, remove /v1/messages suffix
|
||||||
@@ -157,10 +160,9 @@ async function main() {
|
|||||||
|
|
||||||
// Build PresetConfig
|
// Build PresetConfig
|
||||||
const presetConfig: PresetConfig = {
|
const presetConfig: PresetConfig = {
|
||||||
noServer: presetData.noServer,
|
noServer: config.noServer,
|
||||||
claudeCodeSettings: presetData.claudeCodeSettings,
|
claudeCodeSettings: config.claudeCodeSettings,
|
||||||
provider: presetData.provider,
|
StatusLine: config.StatusLine
|
||||||
router: presetData.router,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldStartServer && !isRunning) {
|
if (shouldStartServer && !isRunning) {
|
||||||
@@ -179,7 +181,7 @@ async function main() {
|
|||||||
startProcess.unref();
|
startProcess.unref();
|
||||||
|
|
||||||
if (await waitForService()) {
|
if (await waitForService()) {
|
||||||
executeCodeCommand(codeArgs, presetConfig, envOverrides);
|
executeCodeCommand(codeArgs, presetConfig, envOverrides, command);
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"Service startup timeout, please manually run `ccr start` to start the service"
|
"Service startup timeout, please manually run `ccr start` to start the service"
|
||||||
@@ -192,7 +194,7 @@ async function main() {
|
|||||||
console.error("Service is not running. Please start it first with `ccr start`");
|
console.error("Service is not running. Please start it first with `ccr start`");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
executeCodeCommand(codeArgs, presetConfig, envOverrides);
|
executeCodeCommand(codeArgs, presetConfig, envOverrides, command);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -245,7 +247,9 @@ async function main() {
|
|||||||
process.stdin.on("end", async () => {
|
process.stdin.on("end", async () => {
|
||||||
try {
|
try {
|
||||||
const input: StatusLineInput = JSON.parse(inputData);
|
const input: StatusLineInput = JSON.parse(inputData);
|
||||||
const statusLine = await parseStatusLineData(input);
|
// Check if preset name is provided as argument
|
||||||
|
const presetName = process.argv[3];
|
||||||
|
const statusLine = await parseStatusLineData(input, presetName);
|
||||||
console.log(statusLine);
|
console.log(statusLine);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error parsing status line data:", error);
|
console.error("Error parsing status line data:", error);
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ export interface PresetConfig {
|
|||||||
};
|
};
|
||||||
provider?: string;
|
provider?: string;
|
||||||
router?: Record<string, any>;
|
router?: Record<string, any>;
|
||||||
|
StatusLine?: any; // Preset's StatusLine configuration
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function executeCodeCommand(
|
export async function executeCodeCommand(
|
||||||
args: string[] = [],
|
args: string[] = [],
|
||||||
presetConfig?: PresetConfig | null,
|
presetConfig?: PresetConfig | null,
|
||||||
envOverrides?: Record<string, string>
|
envOverrides?: Record<string, string>,
|
||||||
|
presetName?: string // Preset name for statusline command
|
||||||
) {
|
) {
|
||||||
// Set environment variables using shared function
|
// Set environment variables using shared function
|
||||||
const config = await readConfigFile();
|
const config = await readConfigFile();
|
||||||
@@ -40,11 +42,19 @@ export async function executeCodeCommand(
|
|||||||
env: env as ClaudeSettingsFlag['env']
|
env: env as ClaudeSettingsFlag['env']
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add statusLine if StatusLine is configured
|
// Add statusLine configuration
|
||||||
if (config?.StatusLine?.enabled) {
|
// Priority: preset.StatusLine > global config.StatusLine
|
||||||
|
const statusLineConfig = presetConfig?.StatusLine || config?.StatusLine;
|
||||||
|
|
||||||
|
if (statusLineConfig?.enabled) {
|
||||||
|
// If using preset, pass preset name to statusline command
|
||||||
|
const statuslineCommand = presetName
|
||||||
|
? `ccr statusline ${presetName}`
|
||||||
|
: "ccr statusline";
|
||||||
|
|
||||||
settingsFlag.statusLine = {
|
settingsFlag.statusLine = {
|
||||||
type: "command",
|
type: "command",
|
||||||
command: "ccr statusline",
|
command: statuslineCommand,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { CONFIG_FILE, HOME_DIR } from "@CCR/shared";
|
import { CONFIG_FILE, HOME_DIR, readPresetFile, getPresetDir, loadConfigFromManifest } from "@CCR/shared";
|
||||||
import JSON5 from "json5";
|
import JSON5 from "json5";
|
||||||
|
|
||||||
export interface StatusLineModuleConfig {
|
export interface StatusLineModuleConfig {
|
||||||
@@ -11,7 +11,8 @@ export interface StatusLineModuleConfig {
|
|||||||
text: string;
|
text: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
background?: string;
|
background?: string;
|
||||||
scriptPath?: string; // Path to the Node.js script file to execute for script-type modules
|
scriptPath?: string;
|
||||||
|
options?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatusLineThemeConfig {
|
export interface StatusLineThemeConfig {
|
||||||
@@ -162,7 +163,7 @@ function replaceVariables(text: string, variables: Record<string, string>): stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute script and get output
|
// Execute script and get output
|
||||||
async function executeScript(scriptPath: string, variables: Record<string, string>): Promise<string> {
|
async function executeScript(scriptPath: string, variables: Record<string, string>, options?: Record<string, any>): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
await fs.access(scriptPath);
|
await fs.access(scriptPath);
|
||||||
@@ -172,7 +173,7 @@ async function executeScript(scriptPath: string, variables: Record<string, strin
|
|||||||
|
|
||||||
// If export is a function, call it with variables
|
// If export is a function, call it with variables
|
||||||
if (typeof scriptModule === 'function') {
|
if (typeof scriptModule === 'function') {
|
||||||
const result = scriptModule(variables);
|
const result = scriptModule(variables, options);
|
||||||
// If returns a Promise, wait for it to complete
|
// If returns a Promise, wait for it to complete
|
||||||
if (result instanceof Promise) {
|
if (result instanceof Promise) {
|
||||||
return await result;
|
return await result;
|
||||||
@@ -532,6 +533,37 @@ async function getProjectThemeConfig(): Promise<{ theme: StatusLineThemeConfig |
|
|||||||
return { theme: null, style: 'default' };
|
return { theme: null, style: 'default' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read theme configuration from preset
|
||||||
|
async function getPresetThemeConfig(presetName: string): Promise<{ theme: StatusLineThemeConfig | null, style: string }> {
|
||||||
|
try {
|
||||||
|
// Read preset manifest
|
||||||
|
const manifest = await readPresetFile(presetName);
|
||||||
|
if (!manifest) {
|
||||||
|
return { theme: null, style: 'default' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load preset configuration (applies userValues if present)
|
||||||
|
const presetDir = getPresetDir(presetName);
|
||||||
|
const config = loadConfigFromManifest(manifest, presetDir);
|
||||||
|
|
||||||
|
// Check if there's StatusLine configuration in preset
|
||||||
|
if (config.StatusLine) {
|
||||||
|
// Get current style, default to 'default'
|
||||||
|
const currentStyle = config.StatusLine.currentStyle || 'default';
|
||||||
|
|
||||||
|
// Check if there's configuration for the corresponding style
|
||||||
|
if (config.StatusLine[currentStyle] && config.StatusLine[currentStyle].modules) {
|
||||||
|
return { theme: config.StatusLine[currentStyle], style: currentStyle };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Return null if reading fails
|
||||||
|
// console.error("Failed to read preset theme config:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { theme: null, style: 'default' };
|
||||||
|
}
|
||||||
|
|
||||||
// Check if simple theme should be used (fallback scheme)
|
// Check if simple theme should be used (fallback scheme)
|
||||||
// When environment variable USE_SIMPLE_ICONS is set, or when a terminal that might not support Nerd Fonts is detected
|
// When environment variable USE_SIMPLE_ICONS is set, or when a terminal that might not support Nerd Fonts is detected
|
||||||
function shouldUseSimpleTheme(): boolean {
|
function shouldUseSimpleTheme(): boolean {
|
||||||
@@ -585,36 +617,7 @@ function canDisplayNerdFonts(): boolean {
|
|||||||
return process.env.USE_SIMPLE_ICONS !== 'true';
|
return process.env.USE_SIMPLE_ICONS !== 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if specific Unicode characters can be displayed correctly
|
export async function parseStatusLineData(input: StatusLineInput, presetName?: string): Promise<string> {
|
||||||
// This is a simple heuristic check
|
|
||||||
function canDisplayUnicodeCharacter(char: string): boolean {
|
|
||||||
// For Nerd Font icons, we assume UTF-8 terminals can display them
|
|
||||||
// But accurate detection is difficult, so we rely on environment variables and terminal type detection
|
|
||||||
try {
|
|
||||||
// Check if terminal supports UTF-8
|
|
||||||
const lang = process.env.LANG || process.env.LC_ALL || process.env.LC_CTYPE || '';
|
|
||||||
if (lang.includes('UTF-8') || lang.includes('utf8') || lang.includes('UTF8')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check LC_* environment variables
|
|
||||||
const lcVars = ['LC_ALL', 'LC_CTYPE', 'LANG'];
|
|
||||||
for (const lcVar of lcVars) {
|
|
||||||
const value = process.env[lcVar];
|
|
||||||
if (value && (value.includes('UTF-8') || value.includes('utf8'))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// If check fails, default to true
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, assume it can be displayed
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parseStatusLineData(input: StatusLineInput): Promise<string> {
|
|
||||||
try {
|
try {
|
||||||
// Check if simple theme should be used
|
// Check if simple theme should be used
|
||||||
const useSimpleTheme = shouldUseSimpleTheme();
|
const useSimpleTheme = shouldUseSimpleTheme();
|
||||||
@@ -625,8 +628,24 @@ export async function parseStatusLineData(input: StatusLineInput): Promise<strin
|
|||||||
// Determine which theme to use: use simple theme if user forces it or Nerd Fonts cannot be displayed
|
// Determine which theme to use: use simple theme if user forces it or Nerd Fonts cannot be displayed
|
||||||
const effectiveTheme = useSimpleTheme || !canDisplayNerd ? SIMPLE_THEME : DEFAULT_THEME;
|
const effectiveTheme = useSimpleTheme || !canDisplayNerd ? SIMPLE_THEME : DEFAULT_THEME;
|
||||||
|
|
||||||
// Get theme configuration from home directory, or use the determined default configuration
|
// Get theme configuration: preset config > home directory config > default theme
|
||||||
const { theme: projectTheme, style: currentStyle } = await getProjectThemeConfig();
|
let projectTheme: StatusLineThemeConfig | null = null;
|
||||||
|
let currentStyle = 'default';
|
||||||
|
|
||||||
|
if (presetName) {
|
||||||
|
// Try to get theme configuration from preset first
|
||||||
|
const presetConfig = await getPresetThemeConfig(presetName);
|
||||||
|
projectTheme = presetConfig.theme;
|
||||||
|
currentStyle = presetConfig.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If preset theme not found or no preset specified, try home directory config
|
||||||
|
if (!projectTheme) {
|
||||||
|
const homeConfig = await getProjectThemeConfig();
|
||||||
|
projectTheme = homeConfig.theme;
|
||||||
|
currentStyle = homeConfig.style;
|
||||||
|
}
|
||||||
|
|
||||||
const theme = projectTheme || effectiveTheme;
|
const theme = projectTheme || effectiveTheme;
|
||||||
|
|
||||||
// Get current working directory and Git branch
|
// Get current working directory and Git branch
|
||||||
@@ -784,34 +803,6 @@ export async function parseStatusLineData(input: StatusLineInput): Promise<strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read theme configuration from user home directory (specified style)
|
|
||||||
async function getProjectThemeConfigForStyle(style: string): Promise<StatusLineThemeConfig | null> {
|
|
||||||
try {
|
|
||||||
// Only use fixed configuration file in home directory
|
|
||||||
const configPath = CONFIG_FILE;
|
|
||||||
|
|
||||||
// Check if configuration file exists
|
|
||||||
try {
|
|
||||||
await fs.access(configPath);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configContent = await fs.readFile(configPath, "utf-8");
|
|
||||||
const config = JSON5.parse(configContent);
|
|
||||||
|
|
||||||
// Check if there's StatusLine configuration
|
|
||||||
if (config.StatusLine && config.StatusLine[style] && config.StatusLine[style].modules) {
|
|
||||||
return config.StatusLine[style];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Return null if reading fails
|
|
||||||
// console.error("Failed to read theme config:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render default style status line
|
// Render default style status line
|
||||||
async function renderDefaultStyle(
|
async function renderDefaultStyle(
|
||||||
theme: StatusLineThemeConfig,
|
theme: StatusLineThemeConfig,
|
||||||
@@ -831,7 +822,7 @@ async function renderDefaultStyle(
|
|||||||
// If script type, execute script to get text
|
// If script type, execute script to get text
|
||||||
let text = "";
|
let text = "";
|
||||||
if (module.type === "script" && module.scriptPath) {
|
if (module.type === "script" && module.scriptPath) {
|
||||||
text = await executeScript(module.scriptPath, variables);
|
text = await executeScript(module.scriptPath, variables, module.options);
|
||||||
} else {
|
} else {
|
||||||
text = replaceVariables(module.text, variables);
|
text = replaceVariables(module.text, variables);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ export const createServer = async (config: any): Promise<any> => {
|
|||||||
// Return preset info, config uses the applied userValues configuration
|
// Return preset info, config uses the applied userValues configuration
|
||||||
return {
|
return {
|
||||||
...presetFile,
|
...presetFile,
|
||||||
config: loadConfigFromManifest(manifest),
|
config: loadConfigFromManifest(manifest, presetDir),
|
||||||
userValues: manifest.userValues || {},
|
userValues: manifest.userValues || {},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ export async function listPresets(): Promise<PresetInfo[]> {
|
|||||||
version: manifest.version,
|
version: manifest.version,
|
||||||
description: manifest.description,
|
description: manifest.description,
|
||||||
author: manifest.author,
|
author: manifest.author,
|
||||||
config: loadConfigFromManifest(manifest),
|
config: loadConfigFromManifest(manifest, presetDir),
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore invalid preset directories (no manifest.json or read failed)
|
// Ignore invalid preset directories (no manifest.json or read failed)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Responsible for parsing and validating configuration schema, handling conditional logic and variable replacement
|
* Responsible for parsing and validating configuration schema, handling conditional logic and variable replacement
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
RequiredInput,
|
RequiredInput,
|
||||||
InputType,
|
InputType,
|
||||||
@@ -180,8 +181,8 @@ export function getDynamicOptions(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse provider reference (e.g. {{selectedProvider}})
|
// Parse provider reference (e.g. #{selectedProvider})
|
||||||
const providerId = String(providerField).replace(/^{{(.+)}}$/, '$1');
|
const providerId = String(providerField).replace(/^#{(.+)}$/, '$1');
|
||||||
const selectedProvider = values[providerId];
|
const selectedProvider = values[providerId];
|
||||||
|
|
||||||
if (!selectedProvider || !presetConfig.Providers) {
|
if (!selectedProvider || !presetConfig.Providers) {
|
||||||
@@ -242,7 +243,7 @@ export function resolveOptions(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Template variable replacement
|
* Template variable replacement
|
||||||
* Supports {{variable}} syntax
|
* Supports #{variable} syntax (different from statusline's {{variable}} format)
|
||||||
*/
|
*/
|
||||||
export function replaceTemplateVariables(
|
export function replaceTemplateVariables(
|
||||||
template: any,
|
template: any,
|
||||||
@@ -254,7 +255,7 @@ export function replaceTemplateVariables(
|
|||||||
|
|
||||||
// Handle strings
|
// Handle strings
|
||||||
if (typeof template === 'string') {
|
if (typeof template === 'string') {
|
||||||
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
return template.replace(/#{(\w+)}/g, (_, key) => {
|
||||||
return values[key] !== undefined ? String(values[key]) : '';
|
return values[key] !== undefined ? String(values[key]) : '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -295,9 +296,9 @@ export function applyConfigMappings(
|
|||||||
|
|
||||||
// Resolve value
|
// Resolve value
|
||||||
let value: any;
|
let value: any;
|
||||||
if (typeof mapping.value === 'string' && mapping.value.startsWith('{{')) {
|
if (typeof mapping.value === 'string' && mapping.value.startsWith('#')) {
|
||||||
// Variable reference
|
// Variable reference
|
||||||
const varName = mapping.value.replace(/^{{(.+)}}$/, '$1');
|
const varName = mapping.value.replace(/^#{(.+)}$/, '$1');
|
||||||
value = values[varName];
|
value = values[varName];
|
||||||
} else {
|
} else {
|
||||||
// Fixed value
|
// Fixed value
|
||||||
@@ -338,7 +339,7 @@ export function applyUserInputs(
|
|||||||
const schemaFields = getSchemaFields(presetFile.schema);
|
const schemaFields = getSchemaFields(presetFile.schema);
|
||||||
|
|
||||||
// 1. First apply template (if exists)
|
// 1. First apply template (if exists)
|
||||||
// template completely defines configuration structure, using {{variable}} placeholders
|
// template completely defines configuration structure, using #{variable} placeholders
|
||||||
if (presetFile.template) {
|
if (presetFile.template) {
|
||||||
config = replaceTemplateVariables(presetFile.template, values) as any;
|
config = replaceTemplateVariables(presetFile.template, values) as any;
|
||||||
} else {
|
} else {
|
||||||
@@ -347,7 +348,7 @@ export function applyUserInputs(
|
|||||||
// These fields will be updated or replaced in subsequent configMappings
|
// These fields will be updated or replaced in subsequent configMappings
|
||||||
config = presetFile.config ? { ...presetFile.config } : {};
|
config = presetFile.config ? { ...presetFile.config } : {};
|
||||||
|
|
||||||
// Replace placeholders in config (e.g. {{apiKey}} -> actual value)
|
// Replace placeholders in config (e.g. #{apiKey} -> actual value)
|
||||||
config = replaceTemplateVariables(config, values) as any;
|
config = replaceTemplateVariables(config, values) as any;
|
||||||
|
|
||||||
// Finally, remove schema id fields (they should not appear in final configuration)
|
// Finally, remove schema id fields (they should not appear in final configuration)
|
||||||
@@ -557,7 +558,7 @@ export function buildDependencyGraph(
|
|||||||
if (field.options) {
|
if (field.options) {
|
||||||
const options = field.options as any;
|
const options = field.options as any;
|
||||||
if (options.type === 'models' && options.providerField) {
|
if (options.type === 'models' && options.providerField) {
|
||||||
const providerId = String(options.providerField).replace(/^{{(.+)}}$/, '$1');
|
const providerId = String(options.providerField).replace(/^#{(.+)}$/, '$1');
|
||||||
deps.add(providerId);
|
deps.add(providerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -588,14 +589,78 @@ export function getAffectedFields(
|
|||||||
return affected;
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process StatusLine configuration, convert relative scriptPath to absolute path
|
||||||
|
* @param statusLineConfig StatusLine configuration
|
||||||
|
* @param presetDir Preset directory path
|
||||||
|
*/
|
||||||
|
function processStatusLineConfig(statusLineConfig: any, presetDir?: string): any {
|
||||||
|
if (!statusLineConfig || typeof statusLineConfig !== 'object') {
|
||||||
|
return statusLineConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = { ...statusLineConfig };
|
||||||
|
|
||||||
|
// Process each theme's modules
|
||||||
|
for (const themeKey of Object.keys(result)) {
|
||||||
|
const theme = result[themeKey];
|
||||||
|
if (theme && typeof theme === 'object' && theme.modules) {
|
||||||
|
const modules = Array.isArray(theme.modules) ? theme.modules : [];
|
||||||
|
const processedModules = modules.map((module: any) => {
|
||||||
|
// If module has scriptPath and presetDir is provided, convert to absolute path
|
||||||
|
if (module.scriptPath && presetDir && !module.scriptPath.startsWith('/')) {
|
||||||
|
return {
|
||||||
|
...module,
|
||||||
|
scriptPath: path.join(presetDir, module.scriptPath)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return module;
|
||||||
|
});
|
||||||
|
result[themeKey] = {
|
||||||
|
...theme,
|
||||||
|
modules: processedModules
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process transformers configuration, convert relative path to absolute path
|
||||||
|
* @param transformersConfig Transformers configuration array
|
||||||
|
* @param presetDir Preset directory path
|
||||||
|
*/
|
||||||
|
function processTransformersConfig(transformersConfig: any[], presetDir?: string): any[] {
|
||||||
|
if (!transformersConfig || !Array.isArray(transformersConfig)) {
|
||||||
|
return transformersConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!presetDir) {
|
||||||
|
return transformersConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformersConfig.map((transformer: any) => {
|
||||||
|
// If transformer has path and it's a relative path, convert to absolute path
|
||||||
|
if (transformer.path && !transformer.path.startsWith('/')) {
|
||||||
|
return {
|
||||||
|
...transformer,
|
||||||
|
path: path.join(presetDir, transformer.path)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return transformer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configuration from Manifest and apply userValues
|
* Load configuration from Manifest and apply userValues
|
||||||
* Used when reading installed presets, applying user configuration values at runtime
|
* Used when reading installed presets, applying user configuration values at runtime
|
||||||
*
|
*
|
||||||
* @param manifest Manifest object (contains original configuration and userValues)
|
* @param manifest Manifest object (contains original configuration and userValues)
|
||||||
|
* @param presetDir Optional preset directory path (for resolving relative paths like scriptPath)
|
||||||
* @returns Applied configuration object
|
* @returns Applied configuration object
|
||||||
*/
|
*/
|
||||||
export function loadConfigFromManifest(manifest: ManifestFile): PresetConfigSection {
|
export function loadConfigFromManifest(manifest: ManifestFile, presetDir?: string): PresetConfigSection {
|
||||||
// Convert manifest to PresetFile format
|
// Convert manifest to PresetFile format
|
||||||
const presetFile: PresetFile = {
|
const presetFile: PresetFile = {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -631,11 +696,25 @@ export function loadConfigFromManifest(manifest: ManifestFile): PresetConfigSect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let config: PresetConfigSection;
|
||||||
|
|
||||||
// If userValues exist, apply them
|
// If userValues exist, apply them
|
||||||
if (manifest.userValues && Object.keys(manifest.userValues).length > 0) {
|
if (manifest.userValues && Object.keys(manifest.userValues).length > 0) {
|
||||||
return applyUserInputs(presetFile, manifest.userValues);
|
config = applyUserInputs(presetFile, manifest.userValues);
|
||||||
|
} else {
|
||||||
|
// If no userValues, use original configuration directly
|
||||||
|
config = presetFile.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no userValues, return original configuration directly
|
// Process StatusLine configuration (convert relative scriptPath to absolute path)
|
||||||
return presetFile.config;
|
if (config.StatusLine) {
|
||||||
|
config.StatusLine = processStatusLineConfig(config.StatusLine, presetDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process transformers configuration (convert relative path to absolute path)
|
||||||
|
if (config.transformers) {
|
||||||
|
config.transformers = processTransformersConfig(config.transformers, presetDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export interface DynamicOptions {
|
|||||||
// Automatically extract name and related configuration from preset's Providers
|
// Automatically extract name and related configuration from preset's Providers
|
||||||
|
|
||||||
// Used when type is 'models'
|
// Used when type is 'models'
|
||||||
providerField?: string; // Point to provider selector field path (e.g. "{{selectedProvider}}")
|
providerField?: string; // Point to provider selector field path (e.g. "#{selectedProvider}")
|
||||||
|
|
||||||
// Used when type is 'custom' (reserved)
|
// Used when type is 'custom' (reserved)
|
||||||
source?: string; // Custom data source
|
source?: string; // Custom data source
|
||||||
@@ -152,8 +152,8 @@ export interface PresetConfigSection {
|
|||||||
|
|
||||||
// Template configuration (for dynamically generating configuration based on user input)
|
// Template configuration (for dynamically generating configuration based on user input)
|
||||||
export interface TemplateConfig {
|
export interface TemplateConfig {
|
||||||
// Template configuration using {{variable}} syntax
|
// Template configuration using #{variable} syntax (different from statusline's {{variable}} format)
|
||||||
// Example: { "Providers": [{ "name": "{{providerName}}", "api_key": "{{apiKey}}" }] }
|
// Example: { "Providers": [{ "name": "#{providerName}", "api_key": "#{apiKey}" }] }
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ export interface ConfigMapping {
|
|||||||
target: string;
|
target: string;
|
||||||
|
|
||||||
// Value source (references user input id, or uses fixed value)
|
// Value source (references user input id, or uses fixed value)
|
||||||
value: string | any; // If string and starts with {{, treated as variable reference
|
value: string | any; // If string and starts with #, treated as variable reference (e.g. #{fieldId})
|
||||||
|
|
||||||
// Condition (optional, apply this mapping only when condition is met)
|
// Condition (optional, apply this mapping only when condition is met)
|
||||||
when?: Condition | Condition[];
|
when?: Condition | Condition[];
|
||||||
|
|||||||
Reference in New Issue
Block a user