Terminal-based AI coding agent with interactive TUI for autonomous code generation.
Features: - Interactive TUI with React/Ink - Autonomous agent with tool calls (bash, read, write, edit, glob, grep) - Permission system with pattern-based rules - Session management with auto-compaction - Dual providers: GitHub Copilot and Ollama - MCP server integration - Todo panel and theme system - Streaming responses - GitHub-compatible project context
This commit is contained in:
168
src/services/config.ts
Normal file
168
src/services/config.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Configuration management for CodeTyper CLI
|
||||
*/
|
||||
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import type { Config, Provider } from "@/types/index";
|
||||
import { DIRS, FILES } from "@constants/paths";
|
||||
|
||||
/**
|
||||
* Default configuration values
|
||||
*/
|
||||
const getDefaults = (): Config => ({
|
||||
provider: "copilot",
|
||||
model: "auto",
|
||||
theme: "default",
|
||||
maxIterations: 20,
|
||||
timeout: 30000,
|
||||
cascadeEnabled: true,
|
||||
protectedPaths: [
|
||||
".git",
|
||||
"node_modules",
|
||||
".env",
|
||||
".env.local",
|
||||
".env.production",
|
||||
"dist",
|
||||
"build",
|
||||
".next",
|
||||
"__pycache__",
|
||||
"venv",
|
||||
".venv",
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Environment variable mapping for providers
|
||||
*/
|
||||
const PROVIDER_ENV_VARS: Record<Provider, string> = {
|
||||
copilot: "GITHUB_COPILOT_TOKEN",
|
||||
ollama: "OLLAMA_HOST",
|
||||
};
|
||||
|
||||
/**
|
||||
* Config state (singleton pattern using closure)
|
||||
*/
|
||||
let configState: Config = getDefaults();
|
||||
|
||||
/**
|
||||
* Load configuration from file
|
||||
*/
|
||||
export const loadConfig = async (): Promise<void> => {
|
||||
try {
|
||||
const data = await fs.readFile(FILES.config, "utf-8");
|
||||
const loaded = JSON.parse(data);
|
||||
|
||||
// Clean up deprecated keys
|
||||
delete loaded.models;
|
||||
|
||||
configState = { ...getDefaults(), ...loaded };
|
||||
} catch {
|
||||
// Config file doesn't exist or is invalid, use defaults
|
||||
configState = getDefaults();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Save configuration to file
|
||||
*/
|
||||
export const saveConfig = async (): Promise<void> => {
|
||||
try {
|
||||
await fs.mkdir(DIRS.config, { recursive: true });
|
||||
await fs.writeFile(
|
||||
FILES.config,
|
||||
JSON.stringify(configState, null, 2),
|
||||
"utf-8",
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save config: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get configuration value
|
||||
*/
|
||||
export const getConfigValue = <K extends keyof Config>(key: K): Config[K] => {
|
||||
return configState[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set configuration value
|
||||
*/
|
||||
export const setConfigValue = <K extends keyof Config>(
|
||||
key: K,
|
||||
value: Config[K],
|
||||
): void => {
|
||||
configState[key] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get full configuration
|
||||
*/
|
||||
export const getAllConfig = (): Config => ({ ...configState });
|
||||
|
||||
/**
|
||||
* Get API key for provider from environment
|
||||
*/
|
||||
export const getApiKey = (provider?: Provider): string | undefined => {
|
||||
const targetProvider = provider ?? configState.provider;
|
||||
const envVar = PROVIDER_ENV_VARS[targetProvider];
|
||||
return envVar ? process.env[envVar] : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get configured model
|
||||
*/
|
||||
export const getModel = (): string => {
|
||||
return configState.model ?? "auto";
|
||||
};
|
||||
|
||||
/**
|
||||
* Get config file path
|
||||
*/
|
||||
export const getConfigPath = (): string => FILES.config;
|
||||
|
||||
/**
|
||||
* Check if a path is protected
|
||||
*/
|
||||
export const isProtectedPath = (filePath: string): boolean => {
|
||||
const normalizedPath = path.normalize(filePath);
|
||||
return configState.protectedPaths.some((protectedPath) =>
|
||||
normalizedPath.includes(protectedPath),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset configuration to defaults
|
||||
*/
|
||||
export const resetConfig = (): void => {
|
||||
configState = getDefaults();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize and get config (convenience function)
|
||||
*/
|
||||
export const getConfig = async (): Promise<{
|
||||
get: <K extends keyof Config>(key: K) => Config[K];
|
||||
set: <K extends keyof Config>(key: K, value: Config[K]) => void;
|
||||
getAll: () => Config;
|
||||
save: () => Promise<void>;
|
||||
getApiKey: (provider?: Provider) => string | undefined;
|
||||
getModel: () => string;
|
||||
getConfigPath: () => string;
|
||||
isProtectedPath: (filePath: string) => boolean;
|
||||
reset: () => void;
|
||||
}> => {
|
||||
await loadConfig();
|
||||
return {
|
||||
get: getConfigValue,
|
||||
set: setConfigValue,
|
||||
getAll: getAllConfig,
|
||||
save: saveConfig,
|
||||
getApiKey,
|
||||
getModel,
|
||||
getConfigPath,
|
||||
isProtectedPath,
|
||||
reset: resetConfig,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user