import Conf from 'conf'; export interface Message { role: 'user' | 'assistant' | 'system'; content: string; } type LanguageCode = 'auto' | 'en' | 'zh' | 'es' | 'hi' | 'ar' | 'pt' | 'fr' | 'de' | 'ja' | 'ru' | 'hr'; interface ProjectPermission { path: string; readPermission: boolean; writePermission: boolean; grantedAt: string; } interface ProviderApiKey { providerId: string; apiKey: string; } type AgentMode = 'on' | 'manual' | 'off'; export interface ConfigSchema { apiKey: string; provider: string; model: string; protocol: 'openai' | 'anthropic'; plan: 'lite' | 'pro' | 'max'; language: LanguageCode; autoSave: boolean; /** Auto-generate an LLM one-liner title for sessions on save. Makes a * small background API call (uses the active model) once per session. * Default true; set false to avoid any unsolicited API calls. */ autoSessionTitle: boolean; /** When prior chat history overflows the agent's context budget, summarize * the dropped (oldest) messages via one LLM call instead of silently * discarding them — so long sessions keep early decisions/constraints. * Default true; set false to fall back to plain truncation (no extra call). */ autoSummarizeHistory: boolean; /** Inject the user profile (`~/.codeep/profile.md` + project * `.codeep/profile.md`) into the agent's system prompt so it adapts to the * user (reply language, style, stack, preferences). Default true; set false * to keep the profile files but stop injecting them. Managed via `/me`. */ userProfile: boolean; /** Append a record of what each agent run touched to `.codeep/audit/`. * Reads and refusals included — `history.ts` records neither, because it * exists to undo writes rather than to say what happened. On unless set * false: a record you must remember to enable is not one you can rely on * having when you need it. */ auditLog: boolean; /** Auto-learn: at session save, run one LLM pass to extract durable facts / * preferences about the user and merge them into `~/.codeep/profile.learned.md` * (injected alongside the hand-written profile). OFF by default — opt in via * `/me learn on`. Throttled + single-flight so it doesn't spam API calls. */ autoLearnProfile: boolean; /** Absolute workspace roots whose project-local `.codeep/hooks/*` the user * has approved to run. Untrusted projects' hooks are skipped (a cloned repo * can't execute shell on first tool call). Granted via `/hooks trust`. */ trustedHookProjects: string[]; currentSessionId: string; /** Highest one-shot config migration applied (see the migration block * after config creation). Bump MIGRATION_VERSION when adding one. */ migrationVersion: number; temperature: number; maxTokens: number; /** Thinking / reasoning-effort tier sent with each request. 'auto' (default) * omits the param so each model uses its own default; low/medium/high/max are * clamped per provider+model by reasoningParamsFor() (config/providers.ts). * Only applied for models that expose a graded knob — set via `/thinking`. */ reasoningEffort: 'auto' | 'low' | 'medium' | 'high' | 'max'; apiTimeout: number; rateLimitApi: number; rateLimitCommands: number; agentMode: AgentMode; ollamaUrl: string; /** Route Ollama through its NATIVE /api/chat endpoint instead of the * OpenAI-compatible /v1 shim. The native endpoint honors num_ctx + keep_alive * and exposes the model's real context window. OFF by default — opt-in until * verified against a live Ollama; when off, the existing /v1 path is used * unchanged so current behavior is preserved. */ ollamaNativeApi: boolean; /** How long Ollama keeps the model loaded in memory between requests (e.g. * "30m", "1h", "-1" for forever). Avoids reload latency every turn. Sent on * native /api/chat requests (only when ollamaNativeApi is on). Default "30m". */ ollamaKeepAlive: string; /** Override num_ctx (context window) for Ollama. 0 = auto-detect the model's * real max via /api/show. Set a specific number to cap VRAM use. Default 0. */ ollamaNumCtx: number; customBaseUrl: string; agentConfirmation: 'always' | 'dangerous' | 'never'; /** Also send a pending confirmation to Telegram, so it can be answered away * from the desk. Interactive runs only — a headless run has nobody to ask, * and waiting on an answer that cannot come would hang CI. The bot token * lives in the keychain, never here. */ telegramApproval: boolean; /** Whether a message from the configured chat becomes a prompt. * * Separate from telegramApproval on purpose. Approval lets the phone answer * a question the agent already chose to ask; this lets the phone ask one, * which is a keyboard attached to this machine. Off unless asked for. */ telegramInbox: boolean; /** The single chat allowed to answer. Not a secret — it identifies a * conversation, and it is useless without the token. */ telegramChatId: string; agentConfirmDeleteFile: boolean; agentConfirmExecuteCommand: boolean; agentConfirmWriteFile: boolean; agentAutoCommit: boolean; agentAutoCommitBranch: boolean; agentAutoVerify: 'off' | 'build' | 'typecheck' | 'test' | 'all'; /** After a top-level agent run that changed files, delegate to the `reviewer` * sub-agent and append its findings — a guaranteed review stage. Default * false (opt-in); one extra nested LLM pass when on. */ agentAutoReview: boolean; agentMaxFixAttempts: number; agentMaxIterations: number; agentMaxDuration: number; agentApiTimeout: number; agentInteractive: boolean; projectPermissions: ProjectPermission[]; /** @deprecated Legacy PLAINTEXT key store. Kept only so the one-time * migration into secure storage can read it; emptied afterwards. New keys * go to the OS keychain via utils/keychain.ts — never written here. */ providerApiKeys: ProviderApiKey[]; /** Non-secret index of provider IDs that have a key in secure storage, so we * can list/load configured providers without probing the keychain for all * providers. Secrets themselves never live here. */ configuredProviderIds: string[]; /** True once legacy plaintext keys (providerApiKeys / apiKey) have been * migrated into secure storage and wiped from the config file. */ keysSecured: boolean; /** Plaintext fallback key map used by utils/keychain.ts ONLY when the OS * keychain is unavailable. Swept into the keychain once it becomes available * (see sweepFallbackKeysToKeychain). Empty {} on keychain-capable systems. */ apiKeys?: Record; /** Master switch for automatic cloud uploads (usage stats, session * transcripts, progress, memory notes). Default true; set false to opt out. * The CODEEP_NO_TELEMETRY / DO_NOT_TRACK env vars also force it off. */ telemetry: boolean; /** Opt-in to syncing API keys to codeep.dev (`codeep account push`/`sync`). * OFF by default — keys live only in the OS keychain unless you enable this. * Synced keys are stored server-readable (AES from a server-held secret), so * this is an explicit consent switch. Enable via `/keysync on` or Settings; * the CODEEP_NO_KEY_SYNC env var forces it off (org-policy hard switch). */ syncKeysToCloud: boolean; githubId: string; githubUsername: string; syncToken: string; deviceId: string; /** OpenRouter provider-routing preferences (see utils/openrouterPrefs.ts). */ openrouterPreferences?: { order?: string[]; allow_fallbacks?: boolean; ignore?: string[]; data_collection?: 'allow' | 'deny'; require_parameters?: boolean; }; /** * Active personality preset (`concise`, `senior-reviewer`, custom user * personalities from .codeep/personalities/*.md, …). When set, the * loader text is appended to every agent system prompt. See * utils/personalities.ts. */ activePersonality?: string | null; } export type { AgentMode }; export type { LanguageCode }; /** * Get sessions directory - local .codeep/sessions/ if in project, otherwise global */ export declare function getSessionsDir(projectPath?: string): string; /** * Check if directory is a project * Looks for common project indicators: package.json, pyproject.toml, Cargo.toml, go.mod, composer.json, etc. * Also checks if user has manually initialized this folder as a project (.codeep/project.json) */ export declare function isProjectDirectory(path: string): boolean; /** * Check if directory has standard project markers (not manually initialized) */ export declare function hasStandardProjectMarkers(path: string): boolean; /** * Initialize a folder as a Codeep project * Creates .codeep/project.json marker file */ export declare function initializeAsProject(path: string): boolean; /** * Check if folder was manually initialized as project */ export declare function isManuallyInitializedProject(path: string): boolean; /** * Create config with fallback logic * 1. Try standard Conf location (~/.config/codeep-nodejs on Linux, etc.) * 2. If not writable, use .codeep in current working directory */ /** * What a fresh install starts on. Exported so a test can hold the pair to the * catalogue: the model had stayed `glm-5.2` after Z.AI's default moved to * `glm-5.3`, so new users started a flagship behind what the website promised, * and nothing failed because 5.2 still works. */ export declare const DEFAULT_PROVIDER = "z.ai"; export declare const DEFAULT_MODEL = "glm-5.3"; export declare const config: Conf; export declare const LANGUAGES: Record; export declare const PROTOCOLS: Record; /** * Load API key from config into cache */ export declare function loadApiKey(providerId?: string): Promise; /** * Load API keys for ALL providers into cache * Should be called at app startup */ export declare function loadAllApiKeys(): Promise; /** * Get API key synchronously from cache (must call loadAllApiKeys first) */ export declare function getApiKey(providerId?: string): string; /** * Set API key — persists to secure storage (OS keychain), never plaintext. * Returns a promise, but updates the synchronous cache first so callers that * fire-and-forget still see the key immediately via getApiKey(). */ export declare function setApiKey(key: string, providerId?: string): Promise; export declare function getMaskedApiKey(providerId?: string): string; /** * Get list of providers that have API keys configured */ export declare function getConfiguredProviders(): { id: string; name: string; }[]; export declare function isTelemetryEnabled(): boolean; /** * True when an env var (CODEEP_NO_TELEMETRY / DO_NOT_TRACK) is forcing telemetry * off — in which case the `telemetry` config flag can't turn it back on. Lets * the /telemetry command explain why a toggle had no effect. */ export declare function telemetryForcedOffByEnv(): boolean; export declare function isKeySyncEnabled(): boolean; /** * True when CODEEP_NO_KEY_SYNC is forcing key sync off — so the `syncKeysToCloud` * flag can't turn it back on. Lets the /keysync command explain why a toggle had * no effect. */ export declare function keySyncForcedOffByEnv(): boolean; /** * Clear API key for a specific provider. * * Resolves `true` once the key is gone from secure storage. A keychain that * refuses the delete is swallowed a layer down (keychain.ts logs it at debug), * so the outcome is verified by reading the key back: when it is still there * nothing is changed and this resolves `false` — the caller has to say so * rather than report a logout that left the key on disk. */ export declare function clearApiKey(providerId: string): Promise; export declare function isConfiguredAsync(providerId?: string): Promise; export declare function isConfigured(providerId?: string): boolean; export declare function getCurrentProvider(): { id: string; name: string; }; export declare function setProvider(providerId: string): boolean; export declare function getModelsForCurrentProvider(): Record; export declare function fetchOpenRouterModels(apiKey?: string): Promise<{ id: string; name: string; description: string; }[] | null>; export declare function fetchOllamaModels(baseUrl?: string): Promise<{ id: string; name: string; description: string; }[] | null>; /** * Fetch the model list from an OpenAI-compatible server's `/models` * endpoint (vLLM, LiteLLM, LM Studio, etc.). `baseUrl` is the full base * (e.g. http://host:8000/v1). Returns null on error. */ export declare function fetchOpenAiCompatibleModels(baseUrl: string, apiKey?: string): Promise<{ id: string; name: string; description: string; }[] | null>; /** * Resolve the effective OpenAI-protocol base URL for a provider, honoring * user overrides the static provider table can't express: * - ollama → configured `ollamaUrl` + /v1 * - custom → configured `customBaseUrl` (full base, e.g. http://host:8000/v1) * - openai → the OPENAI_BASE_URL env var, if set (OpenAI-SDK convention) * Falls back to the provider's hardcoded base URL. Only the `openai` * protocol takes overrides; the anthropic protocol uses the static table. */ export declare function resolveBaseUrl(providerId: string, protocol: 'openai' | 'anthropic'): string | null; export { PROVIDERS } from './providers'; export declare function getCurrentSessionId(): string; export declare function startNewSession(): string; export declare function autoSaveSession(history: Message[], projectPath?: string, sessionId?: string): boolean; export declare function flushAutoSave(): boolean; /** * Why a session name cannot be used, or null when it can. A session is kept * as .json inside the sessions directory, so a name holding a path * separator (`../../x`, `feature/auth`) would read or write a file elsewhere. * A backslash is a separator only on Windows; elsewhere it is an ordinary * filename character, and sessions named with one already exist. */ export declare function sessionNameProblem(name: string): string | null; export declare function saveSession(name: string, history: Message[], projectPath?: string): boolean; /** * Generate and persist an AI title for a session, if it doesn't have * one yet. Safe to call repeatedly — early-returns when aiTitle exists * or a generation is already in flight. */ export declare function maybeGenerateSessionTitle(name: string, projectPath?: string): Promise; export declare function loadSession(name: string, projectPath?: string): Message[] | null; export declare function listSessions(projectPath?: string): string[]; export declare function deleteSession(name: string, projectPath?: string): boolean; /** * True when `name` already belongs to a saved conversation other than * `currentName`'s. A name differing only in case can resolve to the same file * on a case-insensitive disk; that is the same conversation, not a clash. * Inodes are compared as bigints: on Windows they can exceed 2^53. */ export declare function sessionNameTaken(name: string, currentName: string, projectPath?: string): boolean; export declare function renameSession(oldName: string, newName: string, projectPath?: string): boolean; export declare function getSessionInfo(name: string, projectPath?: string): { name: string; createdAt: string; messageCount: number; } | null; export interface SessionInfo { name: string; title: string; createdAt: string; messageCount: number; fileSize: number; } /** * List all sessions with metadata, sorted by date (newest first) */ export declare function listSessionsWithInfo(projectPath?: string): SessionInfo[]; /** * Get the permission the user granted for a project. */ export declare function getProjectPermission(projectPath: string): ProjectPermission | null; /** * Record a permission grant in the global config. */ export declare function setProjectPermission(projectPath: string, read: boolean, write: boolean): void; /** * Remove a project's permission from the global config. A grant an older * version wrote into the project for this same directory is removed too, so * it does not linger in a file that may be committed. */ export declare function removeProjectPermission(projectPath: string): boolean; export declare function hasReadPermission(projectPath: string): boolean; export declare function hasWritePermission(projectPath: string): boolean; export declare function getGithubId(): string; export declare function setGithubAccount(githubId: string, username: string): void; export declare function getSyncToken(): string; export declare function setSyncToken(token: string): void; export declare function getDeviceId(): string; export interface Profile { name: string; createdAt: string; provider: string; model: string; protocol: 'openai' | 'anthropic'; temperature: number; maxTokens: number; language: string; agentMode: AgentMode; agentConfirmation: 'always' | 'dangerous' | 'never'; agentAutoCommit: boolean; } export declare function saveProfile(name: string): boolean; export declare function loadProfile(name: string): Profile | null; export declare function applyProfile(profile: Profile): void; export declare function listProfiles(): string[]; export declare function deleteProfile(name: string): boolean; /** * Whether a key can survive being put in an HTTP header. * * `fetch` encodes header values as Latin-1 and throws "Cannot convert argument * to a ByteString" on anything outside it. A key pasted from a web page or a * chat message can pick up a non-breaking space, a zero-width character or a * curly quote, and the resulting failure names neither the key nor the * character — only "the character at index N", counted across the whole header * value with `Bearer ` included, which is not where anyone would look. * * Returns null when the key is fine, or a description that locates the problem * without ever reproducing the key itself. */ export declare function describeUnsendableKey(apiKey: string): string | null;