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:
2026-01-27 23:33:06 -05:00
commit 0062e5d9d9
521 changed files with 66418 additions and 0 deletions

5
src/constants/agent.ts Normal file
View File

@@ -0,0 +1,5 @@
/**
* Agent constants
*/
export const MAX_ITERATIONS = 50;

View File

@@ -0,0 +1,23 @@
/**
* Auto-Scroll Constants
*
* Constants for auto-scroll behavior in the TUI
*/
/** Distance from bottom (in lines) to consider "at bottom" */
export const BOTTOM_THRESHOLD = 3;
/** Settling time after operations complete (ms) */
export const SETTLE_TIMEOUT_MS = 300;
/** Timeout for marking auto-scroll events (ms) */
export const AUTO_SCROLL_MARK_TIMEOUT_MS = 250;
/** Default scroll lines per keyboard event */
export const KEYBOARD_SCROLL_LINES = 3;
/** Default scroll lines per page event */
export const PAGE_SCROLL_LINES = 10;
/** Mouse scroll lines per wheel event */
export const MOUSE_SCROLL_LINES = 3;

103
src/constants/banner.ts Normal file
View File

@@ -0,0 +1,103 @@
/**
* Banner constants for CodeTyper CLI
*/
// ASCII art for "codetyper" using block characters
export const BANNER_LINES = [
" __ __ ",
" _______ _____/ /__ / /___ ______ ___ _____ ",
" / ___/ / / / _ \\/ _ \\/ __/ / / / __ \\/ _ \\/ ___/ ",
"/ /__/ /_/ / __/ __/ /_/ /_/ / /_/ / __/ / ",
"\\___/\\____/\\___/\\___/\\__/\\__, / .___/\\___/_/ ",
" /____/_/ ",
] as const;
// Alternative minimal banner
export const BANNER_MINIMAL = [
"╭───────────────────────────────────────╮",
"│ ▄▀▀ ▄▀▄ █▀▄ ██▀ ▀█▀ ▀▄▀ █▀▄ ██▀ █▀▄ │",
"│ ▀▄▄ ▀▄▀ █▄▀ █▄▄ █ █ █▀ █▄▄ █▀▄ │",
"╰───────────────────────────────────────╯",
] as const;
// Block-style banner (similar to opencode)
export const BANNER_BLOCKS = [
"█▀▀ █▀█ █▀▄ █▀▀ ▀█▀ █▄█ █▀█ █▀▀ █▀█",
"█ █ █ █ █ █▀▀ █ █ █▀▀ █▀▀ █▀▄",
"▀▀▀ ▀▀▀ ▀▀ ▀▀▀ ▀ ▀ ▀ ▀▀▀ ▀ ▀",
] as const;
// Gradient colors for banner (cyan to blue)
export const GRADIENT_COLORS = [
"\x1b[96m", // Bright cyan
"\x1b[36m", // Cyan
"\x1b[94m", // Bright blue
"\x1b[34m", // Blue
"\x1b[95m", // Bright magenta
"\x1b[35m", // Magenta
] as const;
// Banner style to lines mapping
export const BANNER_STYLE_MAP: Record<string, readonly string[]> = {
default: BANNER_LINES,
minimal: BANNER_MINIMAL,
blocks: BANNER_BLOCKS,
} as const;
// Large ASCII art banner
export const BANNER = `
,gggg, _,gggggg,_ ,gggggggggggg, ,ggggggg, ,ggggggggggggggg ,ggg, gg ,ggggggggggg, ,ggggggg, ,ggggggggggg,
,88"""Y8b, ,d8P""d8P"Y8b, dP"""88""""""Y8b, ,dP"""""""Y8bdP""""""88"""""""dP""Y8a 88 dP"""88""""""Y8, ,dP"""""""Y8bdP"""88""""""Y8,
d8" \`Y8,d8' Y8 "8b,dPYb, 88 \`8b, d8' a Y8Yb,_ 88 Yb, \`88 88 Yb, 88 \`8b d8' a Y8Yb, 88 \`8b
d8' 8b d8d8' \`Ybaaad88P' \`" 88 \`8b 88 "Y8P' \`"" 88 \`" 88 88 \`" 88 ,8P 88 "Y8P' \`" 88 ,8P
,8I "Y88P'8P \`"""Y8 88 Y8 \`8baaaa 88 88 88 88aaaad8P" \`8baaaa 88aaaad8P"
I8' 8b d8 88 d8,d8P"""" 88 88 88 88""""" ,d8P"""" 88""""Yb,
d8 Y8, ,8P 88 ,8Pd8" 88 88 ,88 88 d8" 88 "8b
Y8, \`Y8, ,8P' 88 ,8P'Y8, gg, 88 Y8b,___,d888 88 Y8, 88 \`8i
\`Yba,,_____, \`Y8b,,__,,d8P' 88______,dP' \`Yba,,_____, "Yb,,8P "Y88888P"88, 88 \`Yba,,_____, 88 Yb,
\`"Y8888888 \`"Y8888P"' 888888888P" \`"Y8888888 "Y8P' ,ad8888 88 \`"Y8888888 88 Y8
d8P" 88
,d8' 88
d8' 88
88 88
Y8,_ _,88
"Y888P"
`;
// Welcome message with help information
export const WELCOME_MESSAGE = `
🤖 CodeTyper AI Agent - Autonomous Code Generation Assistant
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Default Provider: GitHub Copilot (gpt-4)
Getting Started:
codetyper chat Start interactive chat
codetyper run "your task" Execute autonomous task
codetyper classify "prompt" Analyze intent
codetyper config show View configuration
Commands:
chat Interactive REPL session
run <task> Execute task autonomously
classify <prompt> Classify user intent
plan <intent> Generate execution plan
validate <plan> Validate plan safety
config Manage configuration
serve Start JSON-RPC server
Options:
--help, -h Show help
--version, -V Show version
Chat Commands:
/help Show help
/models View available LLM providers
/provider Switch LLM provider
/files List context files
/clear Clear conversation
/exit Exit chat
💡 Tip: Use 'codetyper chat' then '/models' to see all available providers
📖 Docs: Run 'codetyper --help <command>' for detailed information
`;

30
src/constants/bash.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* Bash tool constants
*/
export const BASH_DEFAULTS = {
MAX_OUTPUT_LENGTH: 30000,
TIMEOUT: 120000,
KILL_DELAY: 1000,
} as const;
export const BASH_SIGNALS = {
TERMINATE: "SIGTERM",
KILL: "SIGKILL",
} as const;
export const BASH_MESSAGES = {
PERMISSION_DENIED: "Permission denied by user",
TIMED_OUT: (timeout: number) => `Command timed out after ${timeout}ms`,
ABORTED: "Command aborted",
EXIT_CODE: (code: number) => `Command exited with code ${code}`,
TRUNCATED: "\n\n... (truncated)",
} as const;
export const BASH_DESCRIPTION = `Execute a shell command. Use this tool to run commands like git, npm, mkdir, etc.
Guidelines:
- Always provide a clear description of what the command does
- Use absolute paths when possible
- Be careful with destructive commands (rm, etc.)
- Commands that modify the filesystem will require user approval`;

View File

@@ -0,0 +1,20 @@
/** Quiet bash commands - read-only exploration operations */
export const QUIET_BASH_PATTERNS = [
/^ls\b/,
/^cat\b/,
/^head\b/,
/^tail\b/,
/^find\b/,
/^grep\b/,
/^rg\b/,
/^fd\b/,
/^tree\b/,
/^pwd\b/,
/^echo\b/,
/^which\b/,
/^file\b/,
/^stat\b/,
/^wc\b/,
/^du\b/,
/^df\b/,
];

View File

@@ -0,0 +1,106 @@
/**
* Chat service constants
*/
export const CHAT_TRUNCATE_DEFAULTS = {
MAX_LINES: 10,
MAX_LENGTH: 500,
} as const;
export const FILE_SIZE_LIMITS = {
MAX_CONTEXT_FILE_SIZE: 100000,
} as const;
export const DIFF_PATTERNS = [
/@@\s*-\d+/m,
/---\s+[ab]?\//m,
/\+\+\+\s+[ab]?\//m,
] as const;
export const GLOB_IGNORE_PATTERNS = [
"**/node_modules/**",
"**/.git/**",
] as const;
export const CHAT_MESSAGES = {
CONVERSATION_CLEARED: "Conversation cleared",
SESSION_SAVED: "Session saved",
LEARNING_SAVED: (scope: string) => `Learning saved (${scope})`,
LEARNING_SKIPPED: "Learning skipped",
NO_LEARNINGS:
"No learnings saved yet. Use /remember to save learnings about your project.",
NO_CONVERSATION:
"No conversation to create learning from. Start a conversation first.",
NO_LEARNINGS_DETECTED:
'No learnings detected from the last exchange. Try being more explicit about preferences (e.g., "always use TypeScript", "prefer functional style").',
UNKNOWN_COMMAND: (cmd: string) => `Unknown command: ${cmd}`,
FILE_NOT_FOUND: (pattern: string) => `File not found: ${pattern}`,
FILE_TOO_LARGE: (name: string, size: number) =>
`File too large: ${name} (${Math.round(size / 1024)}KB)`,
FILE_IS_BINARY: (name: string) => `Cannot add binary file: ${name}`,
FILE_ADDED: (name: string) => `Added to context: ${name}`,
FILE_ADD_FAILED: (error: unknown) => `Failed to add file: ${error}`,
FILE_READ_FAILED: (error: unknown) => `Failed to read file: ${error}`,
ANALYZE_FILES: "Analyze the files I've added to the context.",
GITHUB_ISSUES_FOUND: (count: number, issues: string) =>
`Found ${count} GitHub issue(s): ${issues}`,
COMPACTION_STARTING: "Summarizing conversation history...",
COMPACTION_CONTINUING: "Continuing with your request...",
} as const;
export const AUTH_MESSAGES = {
ALREADY_LOGGED_IN: "Already logged in. Use /logout first to re-authenticate.",
AUTH_SUCCESS: "Successfully authenticated with GitHub Copilot!",
AUTH_FAILED: (error: string) => `Authentication failed: ${error}`,
AUTH_START_FAILED: (error: string) =>
`Failed to start authentication: ${error}`,
LOGGED_OUT:
"Logged out from GitHub Copilot. Run /login to authenticate again.",
NOT_LOGGED_IN: "Not logged in. Run /login to authenticate.",
NO_LOGIN_REQUIRED: (provider: string) =>
`Provider ${provider} doesn't require login.`,
NO_LOGOUT_SUPPORT: (provider: string) =>
`Provider ${provider} doesn't support logout.`,
OLLAMA_NO_AUTH: "Ollama is a local provider - no authentication required.",
COPILOT_AUTH_INSTRUCTIONS: (uri: string, code: string) =>
`To authenticate with GitHub Copilot:\n\n1. Open: ${uri}\n2. Enter code: ${code}\n\nWaiting for authentication...`,
LOGGED_IN_AS: (login: string, name?: string) =>
`Logged in as: ${login}${name ? ` (${name})` : ""}`,
} as const;
export const MODEL_MESSAGES = {
MODEL_AUTO: "Model set to auto - the provider will choose the best model.",
MODEL_CHANGED: (model: string) => `Model changed to: ${model}`,
} as const;
// Re-export HELP_TEXT from prompts for backward compatibility
export { HELP_TEXT } from "@prompts/ui/help";
export const LEARNING_CONFIDENCE_THRESHOLD = 0.7;
export const MAX_LEARNINGS_DISPLAY = 20;
export type CommandName =
| "help"
| "h"
| "clear"
| "c"
| "save"
| "s"
| "context"
| "usage"
| "u"
| "model"
| "models"
| "agent"
| "a"
| "theme"
| "mcp"
| "mode"
| "whoami"
| "login"
| "logout"
| "provider"
| "p"
| "status"
| "remember"
| "learnings";

View File

@@ -0,0 +1,85 @@
/**
* Command suggestion constants
*/
import type { SuggestionPriority } from "@/types/command-suggestion";
export const PROJECT_FILES = {
PACKAGE_JSON: "package.json",
YARN_LOCK: "yarn.lock",
PNPM_LOCK: "pnpm-lock.yaml",
BUN_LOCK: "bun.lockb",
CARGO_TOML: "Cargo.toml",
GO_MOD: "go.mod",
PYPROJECT: "pyproject.toml",
REQUIREMENTS: "requirements.txt",
MAKEFILE: "Makefile",
DOCKERFILE: "Dockerfile",
} as const;
export const PRIORITY_ORDER: Record<SuggestionPriority, number> = {
high: 0,
medium: 1,
low: 2,
};
export const PRIORITY_ICONS: Record<SuggestionPriority, string> = {
high: "⚡",
medium: "→",
low: "·",
};
export const FILE_PATTERNS = {
PACKAGE_JSON: /package\.json$/,
TSCONFIG: /tsconfig.*\.json$/,
SOURCE_FILES: /\.(ts|tsx|js|jsx)$/,
CARGO_TOML: /Cargo\.toml$/,
GO_MOD: /go\.mod$/,
PYTHON_DEPS: /requirements.*\.txt$|pyproject\.toml$/,
DOCKER: /Dockerfile$|docker-compose.*\.ya?ml$/,
MAKEFILE: /Makefile$/,
MIGRATIONS: /migrations?\/.*\.(sql|ts|js)$/,
ENV_EXAMPLE: /\.env\.example$|\.env\.sample$/,
LINTER_CONFIG: /\.eslintrc|\.prettierrc|eslint\.config|prettier\.config/,
TEST_FILE: /\.test\.|\.spec\.|__tests__/,
} as const;
export const CONTENT_PATTERNS = {
DEPENDENCIES: /\"dependencies\"/,
DEV_DEPENDENCIES: /\"devDependencies\"/,
PEER_DEPENDENCIES: /\"peerDependencies\"/,
} as const;
export const SUGGESTION_MESSAGES = {
INSTALL_DEPS: "Install dependencies",
REBUILD_PROJECT: "Rebuild the project",
RUN_TESTS: "Run tests",
START_DEV: "Start development server",
BUILD_RUST: "Build the Rust project",
TIDY_GO: "Tidy Go modules",
INSTALL_PYTHON_EDITABLE: "Install Python package in editable mode",
INSTALL_PYTHON_DEPS: "Install Python dependencies",
DOCKER_COMPOSE_BUILD: "Rebuild and start Docker containers",
DOCKER_BUILD: "Rebuild Docker image",
RUN_MAKE: "Run make",
RUN_MIGRATE: "Run database migrations",
CREATE_ENV: "Create local .env file",
RUN_LINT: "Run linter to check for issues",
} as const;
export const SUGGESTION_REASONS = {
PACKAGE_JSON_MODIFIED: "package.json was modified",
TSCONFIG_CHANGED: "TypeScript configuration changed",
TEST_FILE_MODIFIED: "Test file was modified",
SOURCE_FILE_MODIFIED: "Source file was modified",
CARGO_MODIFIED: "Cargo.toml was modified",
GO_MOD_MODIFIED: "go.mod was modified",
PYTHON_DEPS_CHANGED: "Python dependencies changed",
REQUIREMENTS_MODIFIED: "requirements.txt was modified",
DOCKER_COMPOSE_CHANGED: "Docker Compose configuration changed",
DOCKERFILE_MODIFIED: "Dockerfile was modified",
MAKEFILE_MODIFIED: "Makefile was modified",
MIGRATION_MODIFIED: "Migration file was added or modified",
ENV_TEMPLATE_MODIFIED: "Environment template was modified",
LINTER_CONFIG_CHANGED: "Linter configuration changed",
} as const;

114
src/constants/components.ts Normal file
View File

@@ -0,0 +1,114 @@
/**
* UI component constants
*/
// Box drawing characters
export const BoxChars = {
// Single line
single: {
topLeft: "┌",
topRight: "┐",
bottomLeft: "└",
bottomRight: "┘",
horizontal: "─",
vertical: "│",
leftT: "├",
rightT: "┤",
topT: "┬",
bottomT: "┴",
cross: "┼",
},
// Double line
double: {
topLeft: "╔",
topRight: "╗",
bottomLeft: "╚",
bottomRight: "╝",
horizontal: "═",
vertical: "║",
leftT: "╠",
rightT: "╣",
topT: "╦",
bottomT: "╩",
cross: "╬",
},
// Rounded
rounded: {
topLeft: "╭",
topRight: "╮",
bottomLeft: "╰",
bottomRight: "╯",
horizontal: "─",
vertical: "│",
leftT: "├",
rightT: "┤",
topT: "┬",
bottomT: "┴",
cross: "┼",
},
// Bold
bold: {
topLeft: "┏",
topRight: "┓",
bottomLeft: "┗",
bottomRight: "┛",
horizontal: "━",
vertical: "┃",
leftT: "┣",
rightT: "┫",
topT: "┳",
bottomT: "┻",
cross: "╋",
},
} as const;
// Default box options
export const BOX_DEFAULTS = {
style: "rounded" as const,
padding: 1,
align: "left" as const,
} as const;
// Tool icon mapping
export const TOOL_ICONS = {
bash: "bash",
read: "read",
write: "write",
edit: "edit",
default: "default",
} as const;
// State color mapping
export const STATE_COLORS = {
pending: "DIM",
running: "primary",
success: "success",
error: "error",
} as const;
// Role configuration for message display
export const ROLE_CONFIG = {
user: { label: "You", colorKey: "primary" },
assistant: { label: "CodeTyper", colorKey: "success" },
system: { label: "System", colorKey: "textMuted" },
tool: { label: "Tool", colorKey: "warning" },
} as const;
// Status indicator configuration
export const STATUS_INDICATORS = {
success: { iconKey: "success", colorKey: "success" },
error: { iconKey: "error", colorKey: "error" },
warning: { iconKey: "warning", colorKey: "warning" },
info: { iconKey: "info", colorKey: "info" },
pending: { iconKey: "pending", colorKey: "textMuted" },
running: { iconKey: "running", colorKey: "primary" },
} as const;
// Tool call icon configuration
export const TOOL_CALL_ICONS = {
bash: { iconKey: "bash", colorKey: "warning" },
read: { iconKey: "read", colorKey: "info" },
write: { iconKey: "write", colorKey: "success" },
edit: { iconKey: "edit", colorKey: "primary" },
default: { iconKey: "gear", colorKey: "textMuted" },
} as const;

213
src/constants/copilot.ts Normal file
View File

@@ -0,0 +1,213 @@
import type { ProviderModel, ProviderName } from "@/types/providers";
// Provider identification
export const COPILOT_PROVIDER_NAME: ProviderName = "copilot";
export const COPILOT_DISPLAY_NAME = "GitHub Copilot";
// GitHub Copilot API endpoints
export const COPILOT_AUTH_URL =
"https://api.github.com/copilot_internal/v2/token";
export const COPILOT_MODELS_URL = "https://api.githubcopilot.com/models";
// GitHub OAuth endpoints for device flow
export const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
export const GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
export const GITHUB_ACCESS_TOKEN_URL =
"https://github.com/login/oauth/access_token";
// Cache and retry configuration
export const COPILOT_MODELS_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
export const COPILOT_MAX_RETRIES = 3;
export const COPILOT_INITIAL_RETRY_DELAY = 1000; // 1 second
// Default model
export const COPILOT_DEFAULT_MODEL = "gpt-5-mini";
// Unlimited fallback model (used when quota is exceeded)
export const COPILOT_UNLIMITED_MODEL = "gpt-4o";
// Copilot messages
export const COPILOT_MESSAGES = {
QUOTA_EXCEEDED_SWITCHING: (from: string, to: string) =>
`Quota exceeded for ${from}. Switching to unlimited model: ${to}`,
MODEL_SWITCHED: (from: string, to: string) =>
`Model switched: ${from}${to} (quota exceeded)`,
FORMAT_MULTIPLIER: (multiplier: number) =>
multiplier === 0 ? "Unlimited" : `${multiplier}x`,
} as const;
// Model cost multipliers from GitHub Copilot
// 0x = Unlimited (no premium request usage)
// Lower multiplier = cheaper, Higher multiplier = more expensive
export const MODEL_COST_MULTIPLIERS: Record<string, number> = {
// Unlimited models (0x)
"gpt-4o": 0,
"gpt-4o-mini": 0,
"gpt-5-mini": 0,
"grok-code-fast-1": 0,
"raptor-mini": 0,
// Low cost models (0.33x)
"claude-haiku-4.5": 0.33,
"gemini-3-flash-preview": 0.33,
"gpt-5.1-codex-mini-preview": 0.33,
// Standard cost models (1.0x)
"claude-sonnet-4": 1.0,
"claude-sonnet-4.5": 1.0,
"gemini-2.5-pro": 1.0,
"gemini-3-pro-preview": 1.0,
"gpt-4.1": 1.0,
"gpt-5": 1.0,
"gpt-5-codex-preview": 1.0,
"gpt-5.1": 1.0,
"gpt-5.1-codex": 1.0,
"gpt-5.1-codex-max": 1.0,
"gpt-5.2": 1.0,
"gpt-5.2-codex": 1.0,
// Premium models (3.0x)
"claude-opus-4.5": 3.0,
};
// Models that are unlimited (0x cost multiplier)
export const UNLIMITED_MODELS = new Set([
"gpt-4o",
"gpt-4o-mini",
"gpt-5-mini",
"grok-code-fast-1",
"raptor-mini",
]);
// Model context sizes (input tokens, output tokens)
export interface ModelContextSize {
input: number;
output: number;
}
export const MODEL_CONTEXT_SIZES: Record<string, ModelContextSize> = {
// Claude models
"claude-haiku-4.5": { input: 128000, output: 16000 },
"claude-opus-4.5": { input: 128000, output: 16000 },
"claude-sonnet-4": { input: 128000, output: 16000 },
"claude-sonnet-4.5": { input: 128000, output: 16000 },
// Gemini models
"gemini-2.5-pro": { input: 109000, output: 64000 },
"gemini-3-flash-preview": { input: 109000, output: 64000 },
"gemini-3-pro-preview": { input: 109000, output: 64000 },
// GPT-4 models
"gpt-4.1": { input: 111000, output: 16000 },
"gpt-4o": { input: 64000, output: 4000 },
// GPT-5 models
"gpt-5": { input: 128000, output: 128000 },
"gpt-5-mini": { input: 128000, output: 64000 },
"gpt-5-codex-preview": { input: 128000, output: 128000 },
"gpt-5.1": { input: 128000, output: 64000 },
"gpt-5.1-codex": { input: 128000, output: 128000 },
"gpt-5.1-codex-max": { input: 128000, output: 128000 },
"gpt-5.1-codex-mini-preview": { input: 128000, output: 128000 },
"gpt-5.2": { input: 128000, output: 64000 },
"gpt-5.2-codex": { input: 272000, output: 128000 },
// Other models
"grok-code-fast-1": { input: 109000, output: 64000 },
"raptor-mini": { input: 200000, output: 64000 },
};
// Default context size for unknown models
export const DEFAULT_CONTEXT_SIZE: ModelContextSize = {
input: 128000,
output: 16000,
};
// Get context size for a model
export const getModelContextSize = (modelId: string): ModelContextSize =>
MODEL_CONTEXT_SIZES[modelId] ?? DEFAULT_CONTEXT_SIZE;
// Fallback models when API is unavailable
export const COPILOT_FALLBACK_MODELS: ProviderModel[] = [
{
id: "gpt-4o",
name: "GPT-4o",
maxTokens: 4000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 0,
isUnlimited: true,
},
{
id: "gpt-5-mini",
name: "GPT-5 mini",
maxTokens: 64000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 0,
isUnlimited: true,
},
{
id: "claude-sonnet-4",
name: "Claude Sonnet 4",
maxTokens: 16000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 1.0,
isUnlimited: false,
},
{
id: "claude-sonnet-4.5",
name: "Claude Sonnet 4.5",
maxTokens: 16000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 1.0,
isUnlimited: false,
},
{
id: "claude-opus-4.5",
name: "Claude Opus 4.5",
maxTokens: 16000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 3.0,
isUnlimited: false,
},
{
id: "gpt-4.1",
name: "GPT-4.1",
maxTokens: 16000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 1.0,
isUnlimited: false,
},
{
id: "gpt-5",
name: "GPT-5",
maxTokens: 128000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 1.0,
isUnlimited: false,
},
{
id: "gemini-2.5-pro",
name: "Gemini 2.5 Pro",
maxTokens: 64000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 1.0,
isUnlimited: false,
},
{
id: "grok-code-fast-1",
name: "Grok Code Fast 1",
maxTokens: 64000,
supportsTools: true,
supportsStreaming: true,
costMultiplier: 0,
isUnlimited: true,
},
];

View File

@@ -0,0 +1,42 @@
/**
* Dashboard Constants
*/
export const DASHBOARD_TITLE = "CodeTyper";
export const DASHBOARD_LAYOUT = {
DEFAULT_WIDTH: 120,
CONTENT_HEIGHT: 15,
LEFT_COLUMN_RATIO: 0.35,
PADDING: 3,
} as const;
export const DASHBOARD_LOGO = [
" ██████╗███████╗",
" ██╔════╝██╔════╝",
" ██║ ███████╗",
" ██║ ╚════██║",
" ╚██████╗███████║",
" ╚═════╝╚══════╝",
] as const;
export const DASHBOARD_COMMANDS = [
{ command: "codetyper chat", description: "Start interactive chat" },
{ command: "codetyper run <task>", description: "Execute autonomous task" },
{ command: "/help", description: "Show all commands in chat" },
] as const;
export const DASHBOARD_QUICK_COMMANDS = [
{ command: "codetyper chat", description: "Start interactive chat" },
{ command: "codetyper run", description: "Execute autonomous task" },
{ command: "codetyper --help", description: "Show all commands" },
] as const;
export const DASHBOARD_BORDER = {
TOP_LEFT: "╭",
TOP_RIGHT: "╮",
BOTTOM_LEFT: "╰",
BOTTOM_RIGHT: "╯",
HORIZONTAL: "─",
VERTICAL: "│",
} as const;

13
src/constants/diff.ts Normal file
View File

@@ -0,0 +1,13 @@
/**
* Diff utility constants
*/
// Default context lines for hunks
export const DIFF_CONTEXT_LINES = 3;
// Line type prefixes
export const LINE_PREFIXES = {
add: "+",
remove: "-",
context: " ",
} as const;

25
src/constants/edit.ts Normal file
View File

@@ -0,0 +1,25 @@
/**
* Edit tool constants
*/
export const EDIT_MESSAGES = {
NOT_FOUND:
"Could not find the text to replace. Make sure old_string matches exactly.",
MULTIPLE_OCCURRENCES: (count: number) =>
`old_string appears ${count} times. Use replace_all=true or provide more context to make it unique.`,
PERMISSION_DENIED: "Permission denied by user",
} as const;
export const EDIT_TITLES = {
FAILED: (path: string) => `Edit failed: ${path}`,
CANCELLED: (path: string) => `Edit cancelled: ${path}`,
SUCCESS: (path: string) => `Edited: ${path}`,
EDITING: (name: string) => `Editing ${name}`,
} as const;
export const EDIT_DESCRIPTION = `Edit a file by replacing specific text. The old_string must match exactly.
Guidelines:
- old_string must be unique in the file (or use replace_all)
- Preserve indentation exactly as it appears in the file
- Requires user approval for edits`;

View File

@@ -0,0 +1,30 @@
/**
* Embedding Constants
*
* Configuration for semantic learning retrieval
*/
export const EMBEDDING_DEFAULTS = {
MODEL: "nomic-embed-text",
FALLBACK_MODEL: "all-minilm",
DIMENSIONS: 768,
} as const;
export const EMBEDDING_ENDPOINTS = {
EMBED: "/api/embed",
} as const;
export const EMBEDDING_TIMEOUTS = {
EMBED: 30000,
} as const;
export const EMBEDDING_SEARCH = {
TOP_K: 10,
MIN_SIMILARITY: 0.3,
CACHE_TTL_MS: 300000, // 5 minutes
} as const;
export const EMBEDDING_STORAGE = {
INDEX_FILE: "embeddings.json",
VERSION: 1,
} as const;

View File

@@ -0,0 +1,123 @@
/**
* File Picker constants
*/
export const IGNORED_PATTERNS = [
// Version control
".git",
".svn",
".hg",
// AI/Code assistants
".claude",
".coder",
".codetyper",
".cursor",
".copilot",
".aider",
// Build outputs / binaries
"node_modules",
"dist",
"build",
"bin",
"obj",
"target",
".next",
".nuxt",
".output",
"out",
// Cache directories
".cache",
".turbo",
".parcel-cache",
".vite",
// Test/Coverage
"coverage",
".nyc_output",
// Python
"__pycache__",
".venv",
"venv",
".env",
// OS files
".DS_Store",
"thumbs.db",
// IDE/Editor
".idea",
".vscode",
// Misc
".terraform",
".serverless",
] as const;
export const BINARY_EXTENSIONS = [
// Executables
".exe",
".dll",
".so",
".dylib",
".bin",
".app",
// Images
".png",
".jpg",
".jpeg",
".gif",
".bmp",
".ico",
".webp",
".svg",
".tiff",
// Audio/Video
".mp3",
".mp4",
".wav",
".avi",
".mov",
".mkv",
".flac",
".ogg",
// Archives
".zip",
".tar",
".gz",
".rar",
".7z",
".bz2",
// Documents
".pdf",
".doc",
".docx",
".xls",
".xlsx",
".ppt",
".pptx",
// Fonts
".ttf",
".otf",
".woff",
".woff2",
".eot",
// Database
".db",
".sqlite",
".sqlite3",
// Other binary
".pyc",
".pyo",
".class",
".o",
".a",
".lib",
".node",
".wasm",
] as const;
export type BinaryExtension = (typeof BINARY_EXTENSIONS)[number];
export type IgnoredPattern = (typeof IGNORED_PATTERNS)[number];
export const FILE_PICKER_DEFAULTS = {
MAX_DEPTH: 2,
MAX_RESULTS: 15,
INITIAL_DEPTH: 0,
} as const;

2
src/constants/files.ts Normal file
View File

@@ -0,0 +1,2 @@
// File-related constants
// MAX_FILE_SIZE, etc.

1
src/constants/general.ts Normal file
View File

@@ -0,0 +1 @@
export const PROVIDER_NAME_COPILOT = "copilot";

View File

@@ -0,0 +1,31 @@
/**
* GitHub Issue constants
*/
export const ISSUE_PATTERNS = [
/\bissue\s*#?(\d+)\b/gi,
/\bfix\s+#(\d+)\b/gi,
/\bclose\s+#(\d+)\b/gi,
/\bresolve\s+#(\d+)\b/gi,
/(?<!\w)#(\d+)(?!\w)/g,
] as const;
export const GITHUB_ISSUE_DEFAULTS = {
MAX_ISSUE_NUMBER: 100000,
MIN_ISSUE_NUMBER: 1,
} as const;
export const GITHUB_ISSUE_MESSAGES = {
CONTEXT_HEADER: "The user is referencing the following GitHub issue(s):",
SECTION_SEPARATOR: "\n\n---\n\n",
USER_REQUEST_PREFIX: "User request: ",
UNKNOWN_AUTHOR: "unknown",
} as const;
export const GH_CLI_COMMANDS = {
GET_REMOTE_URL: "git remote get-url origin 2>/dev/null",
VIEW_ISSUE: (issueNumber: number) =>
`gh issue view ${issueNumber} --json number,title,state,body,author,labels,url 2>/dev/null`,
} as const;
export const GITHUB_REMOTE_IDENTIFIER = "github.com";

58
src/constants/glob.ts Normal file
View File

@@ -0,0 +1,58 @@
/**
* Glob tool constants
*/
export const GLOB_DEFAULTS = {
DOT: false,
ONLY_FILES: true,
ONLY_DIRECTORIES: false,
} as const;
export const GLOB_IGNORE_PATTERNS = [
// Version control
"**/.git/**",
"**/.svn/**",
"**/.hg/**",
// AI/Code assistants
"**/.claude/**",
"**/.coder/**",
"**/.codetyper/**",
"**/.cursor/**",
"**/.copilot/**",
"**/.aider/**",
// Build outputs / binaries
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/bin/**",
"**/obj/**",
"**/target/**",
"**/.next/**",
"**/.nuxt/**",
"**/.output/**",
"**/out/**",
// Cache directories
"**/.cache/**",
"**/.turbo/**",
"**/.parcel-cache/**",
"**/.vite/**",
// Test/Coverage
"**/coverage/**",
"**/.nyc_output/**",
// Python
"**/__pycache__/**",
"**/.venv/**",
"**/venv/**",
"**/.env/**",
// IDE/Editor
"**/.idea/**",
"**/.vscode/**",
// Misc
"**/.terraform/**",
"**/.serverless/**",
] as const;
export const GLOB_MESSAGES = {
FAILED: (error: unknown) => `Glob failed: ${error}`,
LIST_FAILED: (error: unknown) => `List failed: ${error}`,
} as const;

28
src/constants/grep.ts Normal file
View File

@@ -0,0 +1,28 @@
/**
* Grep tool constants
*/
export const GREP_DEFAULTS = {
MAX_RESULTS: 100,
DEFAULT_PATTERN: "**/*",
NO_MATCHES_EXIT_CODE: 1,
} as const;
export const GREP_IGNORE_PATTERNS = [
"**/node_modules/**",
"**/.git/**",
"**/dist/**",
"**/build/**",
"**/.next/**",
] as const;
export const GREP_MESSAGES = {
NO_MATCHES: "No matches found",
SEARCH_FAILED: (error: unknown) => `Search failed: ${error}`,
RIPGREP_FAILED: (message: string) => `ripgrep failed: ${message}`,
} as const;
export const GREP_COMMANDS = {
RIPGREP: (pattern: string, directory: string) =>
`rg --line-number --no-heading "${pattern}" "${directory}"`,
} as const;

45
src/constants/handlers.ts Normal file
View File

@@ -0,0 +1,45 @@
/**
* Constants for command handlers
*/
import type { ConfigKey, ConfigAction } from "@/types/handlers";
import type { Provider } from "@/types/index";
export const VALID_CONFIG_KEYS: readonly ConfigKey[] = [
"provider",
"model",
"maxIterations",
"timeout",
] as const;
export const VALID_PROVIDERS: readonly Provider[] = [
"copilot",
"ollama",
] as const;
export const VALID_CONFIG_ACTIONS: readonly ConfigAction[] = [
"show",
"path",
"set",
] as const;
export const CONFIG_VALIDATION = {
MIN_TIMEOUT_MS: 1000,
MIN_ITERATIONS: 1,
} as const;
export const INTENT_KEYWORDS = {
fix: ["fix", "bug"],
test: ["test", "spec"],
refactor: ["refactor", "improve"],
code: ["add", "implement"],
document: ["document", "comment"],
} as const;
export const CLASSIFICATION_CONFIDENCE = {
HIGH: 0.9,
MEDIUM: 0.85,
DEFAULT: 0.8,
LOW: 0.75,
THRESHOLD: 0.7,
} as const;

View File

@@ -0,0 +1,20 @@
export const HELP_COMMANDS: [string, string][] = [
["/help, /h", "Show this help message"],
["/clear, /c", "Clear conversation history"],
["/files, /f", "List files in context"],
["/remove <file>, /rm", "Remove file from context"],
["/context", "Show current context size"],
["/compact", "Compact conversation history"],
["/history", "Show conversation history"],
["/models, /m", "Show available models"],
["/model <name>", "Switch to a different model"],
["/providers, /p", "Show all providers status"],
["/provider <name>", "Switch to a different provider"],
["/agent, /a", "Select agent"],
["/usage, /u", "Show token usage statistics"],
["/mcp [cmd]", "MCP server status/connect/disconnect/tools"],
["/session", "Show current session info"],
["/sessions", "List all saved sessions"],
["/save, /s", "Save current session"],
["/exit, /quit, /q", "Exit chat"],
];

View File

@@ -0,0 +1,35 @@
/**
* Home Screen Constants
* Constants for the welcome/home screen TUI layout
*/
/** Layout constants for home screen */
export const HOME_LAYOUT = {
maxWidth: 75,
topPadding: 3,
bottomPadding: 2,
horizontalPadding: 2,
logoGap: 1,
} as const;
/** Input placeholders shown in the prompt box */
export const PLACEHOLDERS = [
"Fix a TODO in the codebase",
"What is the tech stack of this project?",
"Fix broken tests",
"Explain how this function works",
"Refactor this code for readability",
"Add error handling to this function",
];
/** Keyboard hints displayed below the prompt box */
export const KEYBOARD_HINTS = {
agents: { key: "tab", label: "agents" },
commands: { key: "ctrl+p", label: "commands" },
} as const;
/** MCP status indicators */
export const MCP_INDICATORS = {
connected: "⊙",
error: "⊙",
} as const;

4
src/constants/home.ts Normal file
View File

@@ -0,0 +1,4 @@
export const HOME_VARS = {
title: "Welcome to CodeTyper - Your AI Coding Assistant",
subTitle: "Type a prompt below to start a new session",
};

View File

@@ -0,0 +1,30 @@
/**
* Input editor constants
*/
// Default prompts
export const INPUT_EDITOR_DEFAULTS = {
prompt: "\x1b[36m> \x1b[0m",
continuationPrompt: "\x1b[90m│ \x1b[0m",
} as const;
// ANSI escape sequences
export const ANSI = {
hideCursor: "\x1b[?25l",
showCursor: "\x1b[?25h",
clearLine: "\x1b[2K",
moveUp: (n: number) => `\x1b[${n}A`,
moveDown: (n: number) => `\x1b[${n}B`,
moveRight: (n: number) => `\x1b[${n}C`,
carriageReturn: "\r",
} as const;
// Special key sequences for Alt+Enter
export const ALT_ENTER_SEQUENCES = ["\x1b\r", "\x1b\n"] as const;
// Pasted text styling
export const PASTE_STYLE = {
// Gray/dim style for pasted text placeholder
start: "\x1b[90m",
end: "\x1b[0m",
} as const;

96
src/constants/learning.ts Normal file
View File

@@ -0,0 +1,96 @@
/**
* Learning Service constants
*/
import type { LearningCategory } from "@/types/learning";
export const LEARNING_PATTERNS = [
// User preferences
/always use (\w+)/i,
/prefer (\w+) over (\w+)/i,
/use (\.\w+) files?/i,
/don't use (\w+)/i,
/never use (\w+)/i,
/code in (\w+)/i,
/write in (\w+)/i,
// Project structure
/put .+ in (.+) directory/i,
/files? should be in (.+)/i,
/follow (.+) pattern/i,
/use (.+) architecture/i,
// Coding style
/use (.+) naming convention/i,
/follow (.+) style/i,
/indent with (\w+)/i,
/use (single|double) quotes/i,
// Testing
/use (.+) for testing/i,
/tests? should (.+)/i,
// Dependencies
/use (.+) library/i,
/prefer (.+) package/i,
] as const;
export const LEARNING_KEYWORDS = [
"always",
"never",
"prefer",
"convention",
"standard",
"pattern",
"rule",
"style",
"remember",
"important",
"must",
"should",
] as const;
export const ACKNOWLEDGMENT_PATTERNS = [
/i('ll| will) (use|follow|apply) (.+)/i,
/using (.+) as (you|per your) (requested|preference)/i,
/following (.+) (convention|pattern|style)/i,
/noted.+ (will|going to) (.+)/i,
] as const;
export const ACKNOWLEDGMENT_PHRASES = [
"i understand",
"got it",
"noted",
] as const;
export const LEARNING_DEFAULTS = {
BASE_PATTERN_CONFIDENCE: 0.7,
BASE_KEYWORD_CONFIDENCE: 0.5,
KEYWORD_CONFIDENCE_INCREMENT: 0.1,
ACKNOWLEDGMENT_CONFIDENCE: 0.8,
CONFIDENCE_BOOST: 0.2,
MAX_CONFIDENCE: 1.0,
MIN_KEYWORDS_FOR_LEARNING: 2,
MAX_CONTENT_LENGTH: 80,
TRUNCATE_LENGTH: 77,
MAX_SLICE_LENGTH: 100,
} as const;
export const LEARNING_CONTEXTS = {
USER_PREFERENCE: "User preference",
CONVENTION_IDENTIFIED: "Convention identified",
MULTIPLE_INDICATORS: "Multiple preference indicators",
CONVENTION_CONFIRMED: "Convention confirmed by assistant",
PREFERENCE_ACKNOWLEDGED: "Preference acknowledged by assistant",
} as const;
export const CATEGORY_PATTERNS: Record<string, LearningCategory> = {
prefer: "preference",
use: "preference",
directory: "architecture",
architecture: "architecture",
style: "style",
naming: "style",
indent: "style",
test: "testing",
};

28
src/constants/login.ts Normal file
View File

@@ -0,0 +1,28 @@
/**
* Login flow constants and messages
*/
export const LOGIN_MESSAGES = {
COPILOT_ALREADY_CONFIGURED: "✓ Copilot is already configured and working!",
COPILOT_STARTING_AUTH: "\nStarting GitHub device flow authentication...\n",
COPILOT_AUTH_INSTRUCTIONS: "To authenticate with GitHub Copilot:\n",
COPILOT_WAITING: "Waiting for authentication (press Ctrl+C to cancel)...\n",
COPILOT_SUCCESS: "\n✓ GitHub Copilot authenticated successfully!",
OLLAMA_SUCCESS: "\n✓ Connected to Ollama!",
OLLAMA_NO_MODELS: "\nNo models found. Pull a model with: ollama pull <model>",
AVAILABLE_MODELS: "\nAvailable models:",
VALIDATION_FAILED: "\n✗ Validation failed:",
AUTH_FAILED: "\n✗ Authentication failed:",
CONNECTION_FAILED: "\n✗ Failed to connect:",
UNKNOWN_PROVIDER: "Unknown provider:",
} as const;
export const LOGIN_PROMPTS = {
RECONFIGURE: "Do you want to re-authenticate?",
OLLAMA_HOST: "Ollama host URL:",
} as const;
export const AUTH_STEP_PREFIXES = {
OPEN_URL: " 1. Open:",
ENTER_CODE: " 2. Enter code:",
} as const;

View File

@@ -0,0 +1,41 @@
/**
* Mouse Handler Constants
*
* Constants for terminal mouse event handling
*/
// Mouse event button codes for SGR encoding
export const MOUSE_WHEEL_CODES = {
UP: 64,
DOWN: 65,
} as const;
// Default scroll lines per wheel event
export const MOUSE_SCROLL_LINES = 3;
// SGR mouse sequence pattern: \x1b[<Cb;Cx;Cy(M|m)
export const SGR_MOUSE_REGEX = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
// X10 mouse sequence pattern: \x1b[M followed by 3 bytes
export const X10_MOUSE_REGEX = /\x1b\[M[\x20-\xff]{3}/g;
// Partial/incomplete sequence patterns for cleanup
export const PARTIAL_SGR_REGEX = /\x1b\[<[\d;]*$/;
export const PARTIAL_X10_REGEX = /\x1b\[M.{0,2}$/;
// Mouse tracking escape sequences
export const MOUSE_TRACKING_SEQUENCES = {
ENABLE_BUTTON: "\x1b[?1000h",
ENABLE_SGR: "\x1b[?1006h",
DISABLE_SGR: "\x1b[?1006l",
DISABLE_BUTTON: "\x1b[?1000l",
} as const;
// Scroll direction type
export type MouseScrollDirection = "up" | "down";
// Button code to scroll direction mapping
export const MOUSE_BUTTON_TO_SCROLL: Record<number, MouseScrollDirection> = {
[MOUSE_WHEEL_CODES.UP]: "up",
[MOUSE_WHEEL_CODES.DOWN]: "down",
} as const;

View File

@@ -0,0 +1,38 @@
/**
* Mouse Scroll Constants
*
* Terminal escape sequences for mouse mode handling
*/
// Mouse mode enable/disable escape sequences
export const MOUSE_ESCAPE_SEQUENCES = {
ENABLE: "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h",
DISABLE: "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l",
} as const;
// Mouse button codes
export const MOUSE_BUTTON_CODES = {
SGR_SCROLL_UP: 64,
SGR_SCROLL_DOWN: 65,
X10_SCROLL_UP: 96, // 32 + 64
X10_SCROLL_DOWN: 97, // 32 + 65
} as const;
// SGR mouse mode regex pattern
export const SGR_MOUSE_PATTERN = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/;
// X10/Normal mouse mode prefix
export const X10_MOUSE_PREFIX = "\x1b[M";
export const X10_MIN_LENGTH = 6;
export const X10_BUTTON_OFFSET = 3;
// Scroll direction type
export type ScrollDirection = "up" | "down";
// Mouse button to scroll direction mapping
export const MOUSE_BUTTON_TO_DIRECTION: Record<number, ScrollDirection> = {
[MOUSE_BUTTON_CODES.SGR_SCROLL_UP]: "up",
[MOUSE_BUTTON_CODES.SGR_SCROLL_DOWN]: "down",
[MOUSE_BUTTON_CODES.X10_SCROLL_UP]: "up",
[MOUSE_BUTTON_CODES.X10_SCROLL_DOWN]: "down",
} as const;

32
src/constants/ollama.ts Normal file
View File

@@ -0,0 +1,32 @@
/**
* Ollama provider constants
*/
export const OLLAMA_PROVIDER_NAME = "ollama" as const;
export const OLLAMA_DISPLAY_NAME = "Ollama (Local)";
export const OLLAMA_DEFAULTS = {
BASE_URL: "http://localhost:11434",
MODEL: "deepseek-coder:6.7b",
} as const;
export const OLLAMA_ENDPOINTS = {
TAGS: "/api/tags",
CHAT: "/api/chat",
PULL: "/api/pull",
} as const;
export const OLLAMA_TIMEOUTS = {
VALIDATION: 5000,
CHAT: 120000,
} as const;
export const OLLAMA_CHAT_OPTIONS = {
DEFAULT_TEMPERATURE: 0.3,
DEFAULT_MAX_TOKENS: 4096,
} as const;
export const OLLAMA_ERRORS = {
NOT_RUNNING: (baseUrl: string) =>
`Ollama not running at ${baseUrl}. Start it with: ollama serve`,
} as const;

12
src/constants/paste.ts Normal file
View File

@@ -0,0 +1,12 @@
/**
* Constants for paste virtual text feature
*/
/** Minimum number of lines to trigger paste summary */
export const PASTE_LINE_THRESHOLD = 3;
/** Minimum character count to trigger paste summary */
export const PASTE_CHAR_THRESHOLD = 150;
/** Format for paste placeholder display */
export const PASTE_PLACEHOLDER_FORMAT = "[Pasted ~{lineCount} lines]";

96
src/constants/paths.ts Normal file
View File

@@ -0,0 +1,96 @@
/**
* XDG-compliant storage paths
*
* Follows the XDG Base Directory Specification:
* - XDG_CONFIG_HOME: User configuration (~/.config)
* - XDG_DATA_HOME: User data (~/.local/share)
* - XDG_CACHE_HOME: Cache data (~/.cache)
* - XDG_STATE_HOME: State data (~/.local/state)
*
* See: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
*/
import { homedir } from "os";
import { join } from "path";
const APP_NAME = "codetyper";
/**
* XDG base directories with fallbacks
*/
export const XDG = {
config: process.env.XDG_CONFIG_HOME || join(homedir(), ".config"),
data: process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"),
cache: process.env.XDG_CACHE_HOME || join(homedir(), ".cache"),
state: process.env.XDG_STATE_HOME || join(homedir(), ".local", "state"),
} as const;
/**
* Application directories
*/
export const DIRS = {
/** Configuration directory (~/.config/codetyper) */
config: join(XDG.config, APP_NAME),
/** Data directory (~/.local/share/codetyper) */
data: join(XDG.data, APP_NAME),
/** Cache directory (~/.cache/codetyper) */
cache: join(XDG.cache, APP_NAME),
/** State directory (~/.local/state/codetyper) */
state: join(XDG.state, APP_NAME),
/** Sessions directory (~/.local/share/codetyper/sessions) */
sessions: join(XDG.data, APP_NAME, "sessions"),
} as const;
/**
* Application files
*/
export const FILES = {
/** Main configuration file */
config: join(DIRS.config, "config.json"),
/** Keybindings configuration */
keybindings: join(DIRS.config, "keybindings.json"),
/** Provider credentials (stored in data, not config) */
credentials: join(DIRS.data, "credentials.json"),
/** Command history */
history: join(DIRS.data, "history.json"),
/** Models cache */
modelsCache: join(DIRS.cache, "models.json"),
/** Frecency cache for file/command suggestions */
frecency: join(DIRS.cache, "frecency.json"),
/** Key-value state storage */
kvStore: join(DIRS.state, "kv.json"),
/** Global settings (permissions, etc.) */
settings: join(DIRS.config, "settings.json"),
} as const;
/**
* Local project config directory name
*/
export const LOCAL_CONFIG_DIR = ".codetyper";
export const IGNORE_FOLDERS = [
"**/node_modules/**",
"**/.git/**",
"**/.codetyper/**",
"**/.vscode/**",
"**/.idea/**",
"**/__pycache__/**",
"**/.DS_Store/**",
"**/dist/**",
"**/build/**",
"**/out/**",
"**/.next/**",
"**/.nuxt/**",
"**/venv/**",
];

View File

@@ -0,0 +1 @@
export const FILE_REFERENCE_PATTERN = /@(?:"([^"]+)"|'([^']+)'|(\S+))/g;

View File

@@ -0,0 +1,127 @@
import type { TaskType } from "@/types/provider-quality";
export const QUALITY_THRESHOLDS = {
HIGH: 0.85,
MEDIUM: 0.6,
LOW: 0.4,
INITIAL: 0.5,
} as const;
export const SCORE_ADJUSTMENTS = {
APPROVAL: 0.05,
CORRECTION: -0.08,
USER_REJECTION: -0.15,
MINOR_ISSUE: -0.02,
MAJOR_ISSUE: -0.05,
} as const;
export const TASK_TYPE_PATTERNS: Record<TaskType, RegExp[]> = {
code_generation: [
/create\s+(a\s+)?function/i,
/write\s+(a\s+)?code/i,
/implement/i,
/generate\s+(a\s+)?/i,
/add\s+(a\s+)?(new\s+)?feature/i,
/build\s+(a\s+)?/i,
],
refactoring: [
/refactor/i,
/clean\s*up/i,
/restructure/i,
/reorganize/i,
/simplify/i,
/improve\s+(the\s+)?code/i,
],
bug_fix: [
/fix/i,
/bug/i,
/error/i,
/issue/i,
/not\s+working/i,
/broken/i,
/problem/i,
/debug/i,
],
documentation: [
/document/i,
/comment/i,
/readme/i,
/explain\s+.*\s+code/i,
/add\s+.*\s+docs/i,
/jsdoc/i,
/tsdoc/i,
],
testing: [
/test/i,
/spec/i,
/unit\s+test/i,
/integration/i,
/coverage/i,
/mock/i,
],
explanation: [
/explain/i,
/what\s+(does|is)/i,
/how\s+(does|do)/i,
/why/i,
/understand/i,
/clarify/i,
],
review: [
/review/i,
/check/i,
/evaluate/i,
/assess/i,
/audit/i,
/pr\s+review/i,
],
general: [],
};
export const NEGATIVE_FEEDBACK_PATTERNS = [
/fix\s+this/i,
/that'?s?\s+(wrong|incorrect)/i,
/not\s+(good|right|correct|working)/i,
/doesn'?t?\s+work/i,
/incorrect/i,
/broken/i,
/bad\s+(code|response)/i,
/try\s+again/i,
/redo/i,
/wrong/i,
];
export const POSITIVE_FEEDBACK_PATTERNS = [
/thanks/i,
/thank\s+you/i,
/perfect/i,
/great/i,
/works/i,
/good\s+(job|work)/i,
/excellent/i,
/awesome/i,
/exactly/i,
];
export const DEFAULT_QUALITY_SCORES: Record<TaskType, number> = {
code_generation: QUALITY_THRESHOLDS.INITIAL,
refactoring: QUALITY_THRESHOLDS.INITIAL,
bug_fix: QUALITY_THRESHOLDS.INITIAL,
documentation: QUALITY_THRESHOLDS.INITIAL,
testing: QUALITY_THRESHOLDS.INITIAL,
explanation: QUALITY_THRESHOLDS.INITIAL,
review: QUALITY_THRESHOLDS.INITIAL,
general: QUALITY_THRESHOLDS.INITIAL,
};
export const PROVIDER_IDS = {
OLLAMA: "ollama",
COPILOT: "copilot",
} as const;
export const CASCADE_CONFIG = {
MIN_AUDIT_THRESHOLD: QUALITY_THRESHOLDS.HIGH,
MAX_SKIP_THRESHOLD: QUALITY_THRESHOLDS.LOW,
DECAY_RATE: 0.01,
DECAY_INTERVAL_MS: 7 * 24 * 60 * 60 * 1000, // 1 week
} as const;

View File

@@ -0,0 +1,22 @@
/**
* Provider constants
*/
import type { ProviderInfoRegistry } from "@/types/providers";
export const PROVIDER_INFO: ProviderInfoRegistry = {
copilot: {
envVar: "GitHub OAuth via device flow",
description: "GitHub Copilot - authenticate via GitHub device flow",
},
ollama: {
envVar: "OLLAMA_HOST (default: http://localhost:11434)",
description: "Local Ollama models - no API key needed",
},
};
export const DEFAULT_OLLAMA_HOST = "http://localhost:11434";
export const CREDENTIALS_FILE_MODE = 0o600;
export const MAX_MODELS_DISPLAY = 10;

29
src/constants/read.ts Normal file
View File

@@ -0,0 +1,29 @@
/**
* Read tool constants
*/
export const READ_DEFAULTS = {
MAX_LINES: 2000,
MAX_LINE_LENGTH: 2000,
MAX_BYTES: 100000,
LINE_NUMBER_PAD: 6,
} as const;
export const READ_MESSAGES = {
PERMISSION_DENIED: "Permission denied by user",
} as const;
export const READ_TITLES = {
DENIED: (path: string) => `Read denied: ${path}`,
FAILED: (path: string) => `Read failed: ${path}`,
READING: (name: string) => `Reading ${name}`,
DIRECTORY: (path: string) => `Listed directory: ${path}`,
} as const;
export const READ_DESCRIPTION = `Read the contents of a file. Returns the file content with line numbers.
Guidelines:
- Use absolute paths
- By default reads up to 2000 lines
- Long lines are truncated at 2000 characters
- Use offset and limit for large files`;

250
src/constants/reasoning.ts Normal file
View File

@@ -0,0 +1,250 @@
/**
* Configuration constants for the Reasoning Control Layer
* All tunable parameters in one place
*/
import type {
EntityType,
MemoryItemType,
ValidationCheckType,
} from "@/types/reasoning";
// =============================================================================
// QUALITY EVALUATION THRESHOLDS
// =============================================================================
export const QUALITY_THRESHOLDS = {
ACCEPT: 0.7,
RETRY: 0.4,
ESCALATE: 0.2,
} as const;
export const QUALITY_WEIGHTS = {
structural: 0.3,
relevance: 0.25,
completeness: 0.25,
coherence: 0.2,
} as const;
export const STRUCTURAL_CHECK_WEIGHTS = {
parseSucceeds: 0.4,
hasExpectedFormat: 0.3,
withinLengthBounds: 0.15,
noMalformedBlocks: 0.15,
} as const;
// =============================================================================
// HALLUCINATION DETECTION PATTERNS
// =============================================================================
export const HALLUCINATION_PATTERNS: RegExp[] = [
/I don't have access to .* but/i,
/I cannot .* however/i,
/assuming .* exists/i,
/\[placeholder\]/i,
/TODO:.*implement/i,
/file:\/\/\/[a-z]:/i,
/I'll need to .* first, but/i,
/I'm not able to verify/i,
/hypothetically/i,
/in theory/i,
];
export const CONTRADICTION_PATTERNS: RegExp[] = [
/but actually|actually, no/i,
/wait,? (?:no|I was wrong)/i,
/on second thought/i,
/correction:/i,
/I misspoke/i,
];
export const INCOMPLETE_STATEMENT_PATTERNS: RegExp[] = [
/\.{3,}$/,
/\s+$/,
/(?:and|or|but|if|when|while)\s*$/i,
];
// =============================================================================
// RETRY POLICY LIMITS
// =============================================================================
export const RETRY_LIMITS = {
maxTotalAttempts: 12,
maxPerTier: 2,
maxTimeMs: 60000,
} as const;
export const RETRY_TIER_ORDER = [
"INITIAL",
"RETRY_SAME",
"RETRY_SIMPLIFIED",
"RETRY_DECOMPOSED",
"RETRY_ALTERNATIVE",
"EXHAUSTED",
] as const;
// =============================================================================
// CONTEXT COMPRESSION SETTINGS
// =============================================================================
export const COMPRESSION_THRESHOLDS = {
COMPRESS_AT: 0.8,
MINIMAL_AT: 0.95,
} as const;
export const COMPRESSION_LIMITS = {
maxToolResultTokens: 1000,
truncateHeadTokens: 500,
truncateTailTokens: 500,
maxCodeBlockLines: 30,
keepCodeHeadLines: 10,
keepCodeTailLines: 5,
maxMessageAge: 10,
preserveRecentMessages: 3,
} as const;
// =============================================================================
// MEMORY SELECTION SETTINGS
// =============================================================================
export const MEMORY_WEIGHTS = {
keywordOverlap: 0.25,
entityOverlap: 0.25,
recency: 0.2,
causalLink: 0.15,
pathOverlap: 0.1,
typeBonus: 0.05,
} as const;
export const RECENCY_HALF_LIFE_MINUTES = 30;
export const RELEVANCE_THRESHOLD = 0.15;
export const MEMORY_TYPE_BONUSES: Record<MemoryItemType, number> = {
ERROR: 0.8,
DECISION: 0.6,
TOOL_RESULT: 0.4,
FILE_CONTENT: 0.3,
CONVERSATION: 0.2,
};
export const MANDATORY_MEMORY_AGE_THRESHOLD = 3;
export const ERROR_MEMORY_AGE_THRESHOLD = 10;
// =============================================================================
// TERMINATION DETECTION SETTINGS
// =============================================================================
export const CONFIDENCE_THRESHOLDS = {
CONFIRMED_COMPLETE: 0.85,
POTENTIALLY_COMPLETE: 0.5,
} as const;
export const VALIDATION_TIMEOUT_MS = 60000;
export const VALIDATION_RETRY_COUNT = 1;
export const COMPLETION_SIGNAL_PATTERNS: Array<{
type: "MODEL_STATEMENT";
patterns: RegExp[];
confidence: number;
}> = [
{
type: "MODEL_STATEMENT",
patterns: [
/^(?:I've|I have) (?:completed|finished|done)/i,
/^(?:The|Your) (?:task|request|change) (?:is|has been) (?:complete|done)/i,
/^All (?:changes|modifications) (?:have been|are) (?:made|applied)/i,
/^(?:Done|Finished|Complete)[.!]?$/i,
/successfully (?:created|modified|updated|deleted)/i,
],
confidence: 0.3,
},
];
export const TOOL_SUCCESS_CONFIDENCE = 0.5;
export const OUTPUT_PRESENT_CONFIDENCE = 0.7;
export const NO_PENDING_ACTIONS_CONFIDENCE = 0.4;
export const VALIDATION_CHECK_CONFIGS: Record<
ValidationCheckType,
{
required: boolean;
timeout: number;
}
> = {
FILE_EXISTS: { required: true, timeout: 5000 },
SYNTAX_VALID: { required: true, timeout: 10000 },
DIFF_NONEMPTY: { required: true, timeout: 5000 },
TESTS_PASS: { required: false, timeout: 60000 },
SCHEMA_VALID: { required: false, timeout: 5000 },
NO_REGRESSIONS: { required: false, timeout: 30000 },
};
// =============================================================================
// ENTITY EXTRACTION PATTERNS
// =============================================================================
export const ENTITY_PATTERNS: Record<EntityType, RegExp> = {
FILE: /(?:^|\s|["'`])([\w\-./]+\.[a-z]{1,4})(?:\s|$|:|[()\[\]"'`])/gm,
FUNCTION: /(?:function|def|fn|func|const|let|var)\s+(\w+)\s*[=(]/g,
VARIABLE: /(?:const|let|var|val)\s+(\w+)\s*[=:]/g,
CLASS: /(?:class|struct|interface|type|enum)\s+(\w+)/g,
URL: /https?:\/\/[^\s<>"']+/g,
ERROR_CODE: /(?:error|err|E|errno)\s*[:=]?\s*(\d{3,5})/gi,
};
// =============================================================================
// TOKEN ESTIMATION
// =============================================================================
export const TOKENS_PER_CHAR_ESTIMATE = 0.25;
export const DEFAULT_TOKEN_BUDGET = 8000;
// Default max context (used when model is unknown or "auto")
export const DEFAULT_MAX_CONTEXT_TOKENS = 128000;
// Compaction trigger percentage (compact at 80% of context limit)
export const COMPACTION_TRIGGER_PERCENT = 0.8;
// =============================================================================
// PRESERVATION PRIORITIES
// =============================================================================
export const PRESERVATION_PRIORITIES = {
CONTEXT_FILE: 1.0,
IMAGE: 1.0,
RECENT_MESSAGE: 0.8,
ERROR: 0.7,
DECISION: 0.6,
TOOL_RESULT: 0.4,
OLD_MESSAGE: 0.2,
} as const;
export type PreservationPriority =
(typeof PRESERVATION_PRIORITIES)[keyof typeof PRESERVATION_PRIORITIES];
// =============================================================================
// TASK DECOMPOSITION PATTERNS
// =============================================================================
export const TASK_SEGMENT_PATTERNS: RegExp[] = [
/first,?\s+(.+?)\.\s*then,?\s+(.+)/i,
/(.+?)\s+and\s+(?:also|then)\s+(.+)/i,
/step\s*\d+[:.]\s*(.+)/gi,
/\d+[.)]\s*(.+)/gm,
/[-•]\s*(.+)/gm,
];
// =============================================================================
// EXECUTION PHASE TIMEOUTS
// =============================================================================
export const PHASE_TIMEOUTS: Record<string, number> = {
CONTEXT_PREPARATION: 5000,
LLM_INTERACTION: 120000,
QUALITY_EVALUATION: 1000,
RETRY_DECISION: 500,
EXECUTION: 300000,
TERMINATION_CHECK: 1000,
VALIDATION: 60000,
};

78
src/constants/rules.ts Normal file
View File

@@ -0,0 +1,78 @@
/**
* Constants for project rules loading
*/
/** Locations to search for general rules file */
export const GENERAL_RULES_PATHS = [
"rules.md",
"RULES.md",
".rules.md",
"codetyper.rules.md",
".codetyper/rules.md",
".github/copilot-instructions.md",
".github/INSTRUCTIONS.md",
".github/instructions.md",
] as const;
/** Directories to search for categorized rules */
export const RULES_DIRECTORIES = [
"rules",
".rules",
".codetyper/rules",
".github",
] as const;
/** Well-known MCP categories */
export const MCP_CATEGORIES = [
"figma",
"browser",
"github",
"gitlab",
"slack",
"notion",
"linear",
"jira",
"confluence",
"google-docs",
"google-sheets",
"airtable",
"postgres",
"mysql",
"mongodb",
"redis",
"docker",
"kubernetes",
"aws",
"gcp",
"azure",
"vercel",
"netlify",
"supabase",
"firebase",
] as const;
/** Well-known tool categories */
export const TOOL_CATEGORIES = [
"bash",
"read",
"write",
"edit",
"search",
"git",
] as const;
/** Rule types for categorization */
export const RULE_TYPES = ["mcp", "tool", "custom"] as const;
/** Section titles for formatted rules */
export const RULES_SECTION_TITLES = {
mcp: "MCP Integration Rules",
tool: "Tool Usage Rules",
custom: "Additional Rules",
} as const;
/** Prompt templates for rules */
export const RULES_PROMPT_TEMPLATES = {
PROJECT_RULES_HEADER:
"## Project Rules\n\nThe following rules are specific to this project and must be followed:\n\n",
} as const;

61
src/constants/runner.ts Normal file
View File

@@ -0,0 +1,61 @@
/**
* Runner constants for task execution
*/
import type { StepIconMap } from "@/types/runner";
export const RUNNER_DELAYS = {
DISCOVERY: 1500,
PLANNING: 1000,
STEP_EXECUTION: 1000,
} as const;
export const STEP_ICONS: StepIconMap = {
read: "📖",
edit: "✏️",
create: "📝",
delete: "🗑️",
execute: "⚙️",
} as const;
export const DEFAULT_STEP_ICON = "📌";
export const DEFAULT_FILE = "src/index.ts";
export const RUNNER_MESSAGES = {
DISCOVERY_START: "Discovery phase: Analyzing codebase...",
DISCOVERY_COMPLETE: "Discovery complete",
PLANNING_START: "Planning phase: Creating execution plan...",
DRY_RUN_INFO: "Dry run mode - plan generated but not executed",
EXECUTION_CANCELLED: "Execution cancelled by user",
TASK_COMPLETE: "Task completed successfully!",
TASK_FAILED: "Task failed",
TASK_REQUIRED: "Task description is required",
CONFIRM_EXECUTE: "\nExecute this plan?",
} as const;
export const MOCK_STEPS = {
READ: {
id: "step_1",
type: "read" as const,
description: "Read existing files to understand context",
tool: "view",
},
EDIT: {
id: "step_2",
type: "edit" as const,
description: "Apply necessary changes",
dependencies: ["step_1"],
tool: "edit",
},
EXECUTE: {
id: "step_3",
type: "execute" as const,
description: "Verify changes work correctly",
dependencies: ["step_2"],
tool: "bash",
args: { command: "npm test" },
},
} as const;
export const ESTIMATED_TIME_PER_STEP = 5;

6
src/constants/serve.ts Normal file
View File

@@ -0,0 +1,6 @@
export const SERVER_INFO = `JSON-RPC server mode for Neovim integration.
This will start the agent in server mode, listening for
commands over stdin/stdout using the JSON-RPC protocol.
Press Ctrl+C to stop the server.`;

78
src/constants/spinner.ts Normal file
View File

@@ -0,0 +1,78 @@
/**
* Spinner animation constants
*/
// Spinner frame sets
export const Spinners = {
dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
line: ["-", "\\", "|", "/"],
circle: ["◐", "◓", "◑", "◒"],
arrow: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
bounce: ["⠁", "⠂", "⠄", "⠂"],
bars: [
"▏",
"▎",
"▍",
"▌",
"▋",
"▊",
"▉",
"█",
"▉",
"▊",
"▋",
"▌",
"▍",
"▎",
"▏",
],
pulse: ["█", "▓", "▒", "░", "▒", "▓"],
blocks: ["▖", "▘", "▝", "▗"],
clock: [
"🕐",
"🕑",
"🕒",
"🕓",
"🕔",
"🕕",
"🕖",
"🕗",
"🕘",
"🕙",
"🕚",
"🕛",
],
moon: ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"],
braille: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
scanner: [
"[ ]",
"[= ]",
"[== ]",
"[=== ]",
"[ ===]",
"[ ==]",
"[ =]",
"[ ]",
],
} as const;
// Default spinner configuration
export const SPINNER_DEFAULTS = {
type: "dots" as const,
interval: 80,
text: "Loading...",
} as const;
// Scanner spinner defaults
export const SCANNER_DEFAULTS = {
width: 10,
interval: 60,
char: "█",
} as const;
// Progress bar defaults
export const PROGRESS_BAR_DEFAULTS = {
width: 30,
filledChar: "█",
emptyChar: "░",
} as const;

View File

@@ -0,0 +1,238 @@
/**
* Whimsical status messages for different operations
*/
// General thinking messages
export const THINKING_MESSAGES = [
"Pondering",
"Contemplating",
"Cogitating",
"Ruminating",
"Deliberating",
"Musing",
"Brainstorming",
"Noodling",
"Percolating",
"Synthesizing",
"Ideating",
"Mulling it over",
"Connecting dots",
"Brewing thoughts",
"Hatching ideas",
] as const;
// Tool-specific messages
export const BASH_MESSAGES = [
"Executing",
"Running",
"Invoking",
"Launching",
"Firing up",
"Spinning up",
"Kickstarting",
] as const;
export const READ_MESSAGES = [
"Reading",
"Scanning",
"Perusing",
"Examining",
"Inspecting",
"Analyzing",
"Studying",
"Digesting",
] as const;
export const WRITE_MESSAGES = [
"Writing",
"Crafting",
"Composing",
"Authoring",
"Scribing",
"Generating",
"Creating",
"Materializing",
] as const;
export const EDIT_MESSAGES = [
"Editing",
"Refining",
"Polishing",
"Tweaking",
"Adjusting",
"Massaging",
"Tuning",
"Perfecting",
] as const;
export const GLOB_MESSAGES = [
"Searching",
"Hunting",
"Scouring",
"Exploring",
"Traversing",
"Scanning",
] as const;
export const GREP_MESSAGES = [
"Grepping",
"Searching",
"Pattern matching",
"Filtering",
"Sifting through",
] as const;
export const TOOL_MESSAGES: Record<string, readonly string[]> = {
bash: BASH_MESSAGES,
read: READ_MESSAGES,
write: WRITE_MESSAGES,
edit: EDIT_MESSAGES,
glob: GLOB_MESSAGES,
grep: GREP_MESSAGES,
};
// Fallback messages for unknown tools
export const GENERIC_TOOL_MESSAGES = [
"Processing",
"Working on",
"Handling",
"Tackling",
"Attending to",
"Taking care of",
] as const;
// Fun filler words that can be combined
export const FILLER_ACTIONS = [
"Flibbertigibbeting",
"Discombobulating",
"Recalibrating",
"Defenestrating bugs",
"Wrangling bits",
"Herding electrons",
"Caffeinating",
"Quantum entangling",
"Reverse engineering gravity",
"Consulting the oracle",
"Aligning chakras",
"Summoning expertise",
"Channeling wisdom",
"Marshalling resources",
"Orchestrating magic",
] as const;
// Messages for specific file types
export const FILE_TYPE_MESSAGES: Record<string, string[]> = {
ts: ["TypeScripting", "Type-checking", "Transpiling thoughts"],
tsx: ["Rendering components", "JSX-ing", "Reactifying"],
js: ["JavaScripting", "Interpreting"],
json: ["Parsing JSON", "Structuring data"],
md: ["Marking down", "Documenting"],
css: ["Styling", "Beautifying"],
html: ["Marking up", "Structuring"],
py: ["Pythoning", "Slithering through code"],
rs: ["Rusting", "Borrowing safely"],
go: ["Going", "Goroutining"],
sql: ["Querying", "Selecting wisdom"],
yaml: ["YAMLing", "Indenting carefully"],
toml: ["TOMLing", "Configuring"],
sh: ["Shelling", "Bashing"],
};
/**
* Get a random message from an array
*/
function randomFrom<T>(arr: readonly T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
/**
* Get a thinking message
*/
export function getThinkingMessage(): string {
// 20% chance of a fun filler message
if (Math.random() < 0.2) {
return `${randomFrom(FILLER_ACTIONS)}`;
}
return `${randomFrom(THINKING_MESSAGES)}`;
}
/**
* Get a tool execution message based on tool name and optional file path
*/
export function getToolMessage(toolName: string, filePath?: string): string {
// Check for file-type specific messages
if (filePath) {
const ext = filePath.split(".").pop()?.toLowerCase();
if (ext && FILE_TYPE_MESSAGES[ext]) {
return `${randomFrom(FILE_TYPE_MESSAGES[ext])}`;
}
}
// Get tool-specific messages
const messages = TOOL_MESSAGES[toolName] || GENERIC_TOOL_MESSAGES;
return `${randomFrom(messages)}`;
}
/**
* Get a contextual message based on what's happening
*/
export function getContextualMessage(context: {
mode: "thinking" | "tool_execution";
toolName?: string;
filePath?: string;
description?: string;
}): string {
if (context.mode === "thinking") {
return getThinkingMessage();
}
if (context.toolName) {
return getToolMessage(context.toolName, context.filePath);
}
return `${randomFrom(GENERIC_TOOL_MESSAGES)}`;
}
/**
* Status message rotator - cycles through messages at intervals
*/
export class StatusMessageRotator {
private intervalId: ReturnType<typeof setInterval> | null = null;
private currentMode: "thinking" | "tool_execution" = "thinking";
private toolContext: { name?: string; path?: string } = {};
start(onMessage: (message: string) => void, intervalMs = 3000): void {
this.stop();
// Emit initial message
onMessage(this.getMessage());
// Rotate messages
this.intervalId = setInterval(() => {
onMessage(this.getMessage());
}, intervalMs);
}
stop(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
setMode(mode: "thinking" | "tool_execution"): void {
this.currentMode = mode;
}
setToolContext(name?: string, path?: string): void {
this.toolContext = { name, path };
}
private getMessage(): string {
return getContextualMessage({
mode: this.currentMode,
toolName: this.toolContext.name,
filePath: this.toolContext.path,
});
}
}

95
src/constants/styles.ts Normal file
View File

@@ -0,0 +1,95 @@
/**
* ANSI color styles constants for terminal output
*/
// ANSI escape codes
export const Style = {
// Reset
RESET: "\x1b[0m",
// Text styles
BOLD: "\x1b[1m",
DIM: "\x1b[2m",
ITALIC: "\x1b[3m",
UNDERLINE: "\x1b[4m",
// Colors
BLACK: "\x1b[30m",
RED: "\x1b[31m",
GREEN: "\x1b[32m",
YELLOW: "\x1b[33m",
BLUE: "\x1b[34m",
MAGENTA: "\x1b[35m",
CYAN: "\x1b[36m",
WHITE: "\x1b[37m",
GRAY: "\x1b[90m",
// Bright colors
BRIGHT_RED: "\x1b[91m",
BRIGHT_GREEN: "\x1b[92m",
BRIGHT_YELLOW: "\x1b[93m",
BRIGHT_BLUE: "\x1b[94m",
BRIGHT_MAGENTA: "\x1b[95m",
BRIGHT_CYAN: "\x1b[96m",
BRIGHT_WHITE: "\x1b[97m",
// Background colors
BG_BLACK: "\x1b[40m",
BG_RED: "\x1b[41m",
BG_GREEN: "\x1b[42m",
BG_YELLOW: "\x1b[43m",
BG_BLUE: "\x1b[44m",
BG_MAGENTA: "\x1b[45m",
BG_CYAN: "\x1b[46m",
BG_WHITE: "\x1b[47m",
} as const;
// Semantic colors
export const Theme = {
primary: Style.BRIGHT_CYAN,
secondary: Style.BRIGHT_MAGENTA,
accent: Style.BRIGHT_BLUE,
success: Style.BRIGHT_GREEN,
warning: Style.BRIGHT_YELLOW,
error: Style.BRIGHT_RED,
info: Style.BRIGHT_BLUE,
text: Style.WHITE,
textMuted: Style.GRAY,
textDim: Style.DIM,
} as const;
// Icons
export const Icons = {
// Status
success: "\u2714", // ✔
error: "\u2718", // ✘
warning: "\u26A0", // ⚠
info: "\u2139", //
pending: "\u25CB", // ○
running: "\u25CF", // ●
// Actions
arrow: "\u2192", // →
arrowLeft: "\u2190", // ←
arrowUp: "\u2191", // ↑
arrowDown: "\u2193", // ↓
// Tools
bash: "$",
read: "\u2192", // →
write: "\u2190", // ←
edit: "\u270E", // ✎
search: "\u2731", // ✱
// Misc
bullet: "\u2022", // •
dot: "\u00B7", // ·
star: "\u2605", // ★
sparkle: "\u2728", // ✨
lightning: "\u26A1", // ⚡
gear: "\u2699", // ⚙
key: "\u1F511", // 🔑
lock: "\u1F512", // 🔒
folder: "\u1F4C1", // 📁
file: "\u1F4C4", // 📄
} as const;

View File

@@ -0,0 +1,170 @@
/**
* Syntax highlighting constants
*/
/** Map file extensions to highlight.js language identifiers */
export const EXTENSION_TO_LANGUAGE: Record<string, string> = {
// JavaScript/TypeScript
".js": "javascript",
".jsx": "javascript",
".ts": "typescript",
".tsx": "typescript",
".mjs": "javascript",
".cjs": "javascript",
// Web
".html": "html",
".htm": "html",
".css": "css",
".scss": "scss",
".sass": "scss",
".less": "less",
".vue": "vue",
".svelte": "svelte",
// Data formats
".json": "json",
".yaml": "yaml",
".yml": "yaml",
".toml": "toml",
".xml": "xml",
".csv": "plaintext",
// Systems programming
".c": "c",
".h": "c",
".cpp": "cpp",
".cc": "cpp",
".cxx": "cpp",
".hpp": "cpp",
".rs": "rust",
".go": "go",
".zig": "zig",
// JVM languages
".java": "java",
".kt": "kotlin",
".kts": "kotlin",
".scala": "scala",
".groovy": "groovy",
".gradle": "groovy",
// .NET languages
".cs": "csharp",
".fs": "fsharp",
".vb": "vbnet",
// Scripting
".py": "python",
".rb": "ruby",
".php": "php",
".pl": "perl",
".lua": "lua",
".r": "r",
".R": "r",
// Shell
".sh": "bash",
".bash": "bash",
".zsh": "bash",
".fish": "fish",
".ps1": "powershell",
".psm1": "powershell",
".bat": "batch",
".cmd": "batch",
// Config files
".conf": "nginx",
".ini": "ini",
".env": "bash",
".dockerfile": "dockerfile",
".gitignore": "plaintext",
".editorconfig": "ini",
// Documentation
".md": "markdown",
".markdown": "markdown",
".rst": "plaintext",
".txt": "plaintext",
// Database
".sql": "sql",
".prisma": "prisma",
// Mobile
".swift": "swift",
".m": "objectivec",
".mm": "objectivec",
".dart": "dart",
// Other
".ex": "elixir",
".exs": "elixir",
".erl": "erlang",
".hrl": "erlang",
".hs": "haskell",
".clj": "clojure",
".cljs": "clojure",
".elm": "elm",
".nim": "nim",
".v": "v",
".asm": "x86asm",
".s": "x86asm",
".wasm": "wasm",
".graphql": "graphql",
".gql": "graphql",
".tf": "hcl",
".hcl": "hcl",
".nix": "nix",
".proto": "protobuf",
} as const;
/** Special filename mappings */
export const FILENAME_TO_LANGUAGE: Record<string, string> = {
Dockerfile: "dockerfile",
Makefile: "makefile",
"CMakeLists.txt": "cmake",
Gemfile: "ruby",
Rakefile: "ruby",
Vagrantfile: "ruby",
Brewfile: "ruby",
".bashrc": "bash",
".zshrc": "bash",
".bash_profile": "bash",
".profile": "bash",
"package.json": "json",
"tsconfig.json": "json",
"jsconfig.json": "json",
".prettierrc": "json",
".eslintrc": "json",
"nginx.conf": "nginx",
"docker-compose.yml": "yaml",
"docker-compose.yaml": "yaml",
} as const;
/** Human-readable language display names */
export const LANGUAGE_DISPLAY_NAMES: Record<string, string> = {
javascript: "JavaScript",
typescript: "TypeScript",
python: "Python",
java: "Java",
cpp: "C++",
csharp: "C#",
go: "Go",
rust: "Rust",
ruby: "Ruby",
php: "PHP",
swift: "Swift",
kotlin: "Kotlin",
scala: "Scala",
html: "HTML",
css: "CSS",
scss: "SCSS",
json: "JSON",
yaml: "YAML",
markdown: "Markdown",
bash: "Bash",
sql: "SQL",
dockerfile: "Dockerfile",
graphql: "GraphQL",
} as const;

View File

@@ -0,0 +1,9 @@
/**
* Mouse tracking disable sequence (all modes)
*/
export const DISABLE_MOUSE_TRACKING =
"\x1b[?1006l" + // SGR Mouse Mode
"\x1b[?1015l" + // urxvt Mouse Mode
"\x1b[?1003l" + // All mouse events (motion)
"\x1b[?1002l" + // Button event mouse tracking
"\x1b[?1000l"; // Normal tracking mode

834
src/constants/themes.ts Normal file
View File

@@ -0,0 +1,834 @@
/**
* Built-in Theme Definitions
*
* Provides pre-configured color themes for the TUI
*/
import type { Theme, ThemeColors } from "@/types/theme";
const DEFAULT_COLORS: ThemeColors = {
primary: "#00ffff",
secondary: "#0088ff",
accent: "#ff00ff",
success: "#00ff00",
error: "#ff0000",
warning: "#ffff00",
info: "#00ffff",
text: "#ffffff",
textDim: "#808080",
textMuted: "#666666",
background: "#0a0a0a",
backgroundPanel: "#141414",
backgroundElement: "#1e1e1e",
border: "#808080",
borderFocus: "#00ffff",
borderWarning: "#ffff00",
borderModal: "#ff00ff",
bgHighlight: "#00ffff",
bgCursor: "#00ffff",
bgAdded: "#00ff88",
bgRemoved: "#ff4444",
diffAdded: "#00ff00",
diffRemoved: "#ff0000",
diffContext: "#808080",
diffHeader: "#ffffff",
diffHunk: "#00ffff",
roleUser: "#00ffff",
roleAssistant: "#00ff00",
roleSystem: "#ffff00",
roleTool: "#ffff00",
modeIdle: "#00ff00",
modeEditing: "#00ffff",
modeThinking: "#ff00ff",
modeToolExecution: "#ffff00",
modePermission: "#ffff00",
toolPending: "#808080",
toolRunning: "#ffff00",
toolSuccess: "#00ff00",
toolError: "#ff0000",
headerGradient: ["#00ffff", "#00dddd", "#0088ff"],
};
const DRACULA_COLORS: ThemeColors = {
primary: "#bd93f9",
secondary: "#6272a4",
accent: "#ff79c6",
success: "#50fa7b",
error: "#ff5555",
warning: "#f1fa8c",
info: "#8be9fd",
text: "#f8f8f2",
textDim: "#6272a4",
textMuted: "#44475a",
background: "#282a36",
backgroundPanel: "#21222c",
backgroundElement: "#343746",
border: "#44475a",
borderFocus: "#bd93f9",
borderWarning: "#f1fa8c",
borderModal: "#ff79c6",
bgHighlight: "#44475a",
bgCursor: "#bd93f9",
bgAdded: "#50fa7b",
bgRemoved: "#ff5555",
diffAdded: "#50fa7b",
diffRemoved: "#ff5555",
diffContext: "#6272a4",
diffHeader: "#f8f8f2",
diffHunk: "#8be9fd",
roleUser: "#8be9fd",
roleAssistant: "#50fa7b",
roleSystem: "#f1fa8c",
roleTool: "#ffb86c",
modeIdle: "#50fa7b",
modeEditing: "#8be9fd",
modeThinking: "#bd93f9",
modeToolExecution: "#f1fa8c",
modePermission: "#ffb86c",
toolPending: "#6272a4",
toolRunning: "#f1fa8c",
toolSuccess: "#50fa7b",
toolError: "#ff5555",
headerGradient: ["#bd93f9", "#ff79c6", "#8be9fd"],
};
const NORD_COLORS: ThemeColors = {
primary: "#88c0d0",
secondary: "#5e81ac",
accent: "#b48ead",
success: "#a3be8c",
error: "#bf616a",
warning: "#ebcb8b",
info: "#88c0d0",
text: "#eceff4",
textDim: "#4c566a",
textMuted: "#3b4252",
background: "#2e3440",
backgroundPanel: "#3b4252",
backgroundElement: "#434c5e",
border: "#3b4252",
borderFocus: "#88c0d0",
borderWarning: "#ebcb8b",
borderModal: "#b48ead",
bgHighlight: "#3b4252",
bgCursor: "#88c0d0",
bgAdded: "#a3be8c",
bgRemoved: "#bf616a",
diffAdded: "#a3be8c",
diffRemoved: "#bf616a",
diffContext: "#4c566a",
diffHeader: "#eceff4",
diffHunk: "#81a1c1",
roleUser: "#88c0d0",
roleAssistant: "#a3be8c",
roleSystem: "#ebcb8b",
roleTool: "#d08770",
modeIdle: "#a3be8c",
modeEditing: "#88c0d0",
modeThinking: "#b48ead",
modeToolExecution: "#ebcb8b",
modePermission: "#d08770",
toolPending: "#4c566a",
toolRunning: "#ebcb8b",
toolSuccess: "#a3be8c",
toolError: "#bf616a",
headerGradient: ["#88c0d0", "#81a1c1", "#5e81ac"],
};
const TOKYO_NIGHT_COLORS: ThemeColors = {
primary: "#7aa2f7",
secondary: "#565f89",
accent: "#bb9af7",
success: "#9ece6a",
error: "#f7768e",
warning: "#e0af68",
info: "#7dcfff",
text: "#c0caf5",
textDim: "#565f89",
textMuted: "#414868",
background: "#1a1b26",
backgroundPanel: "#16161e",
backgroundElement: "#24283b",
border: "#414868",
borderFocus: "#7aa2f7",
borderWarning: "#e0af68",
borderModal: "#bb9af7",
bgHighlight: "#414868",
bgCursor: "#7aa2f7",
bgAdded: "#9ece6a",
bgRemoved: "#f7768e",
diffAdded: "#9ece6a",
diffRemoved: "#f7768e",
diffContext: "#565f89",
diffHeader: "#c0caf5",
diffHunk: "#7dcfff",
roleUser: "#7dcfff",
roleAssistant: "#9ece6a",
roleSystem: "#e0af68",
roleTool: "#ff9e64",
modeIdle: "#9ece6a",
modeEditing: "#7aa2f7",
modeThinking: "#bb9af7",
modeToolExecution: "#e0af68",
modePermission: "#ff9e64",
toolPending: "#565f89",
toolRunning: "#e0af68",
toolSuccess: "#9ece6a",
toolError: "#f7768e",
headerGradient: ["#7aa2f7", "#bb9af7", "#7dcfff"],
};
const GRUVBOX_COLORS: ThemeColors = {
primary: "#83a598",
secondary: "#458588",
accent: "#d3869b",
success: "#b8bb26",
error: "#fb4934",
warning: "#fabd2f",
info: "#83a598",
text: "#ebdbb2",
textDim: "#665c54",
textMuted: "#504945",
background: "#282828",
backgroundPanel: "#1d2021",
backgroundElement: "#3c3836",
border: "#504945",
borderFocus: "#83a598",
borderWarning: "#fabd2f",
borderModal: "#d3869b",
bgHighlight: "#504945",
bgCursor: "#83a598",
bgAdded: "#b8bb26",
bgRemoved: "#fb4934",
diffAdded: "#b8bb26",
diffRemoved: "#fb4934",
diffContext: "#665c54",
diffHeader: "#ebdbb2",
diffHunk: "#8ec07c",
roleUser: "#83a598",
roleAssistant: "#b8bb26",
roleSystem: "#fabd2f",
roleTool: "#fe8019",
modeIdle: "#b8bb26",
modeEditing: "#83a598",
modeThinking: "#d3869b",
modeToolExecution: "#fabd2f",
modePermission: "#fe8019",
toolPending: "#665c54",
toolRunning: "#fabd2f",
toolSuccess: "#b8bb26",
toolError: "#fb4934",
headerGradient: ["#b8bb26", "#83a598", "#fabd2f"],
};
const MONOKAI_COLORS: ThemeColors = {
primary: "#66d9ef",
secondary: "#ae81ff",
accent: "#f92672",
success: "#a6e22e",
error: "#f92672",
warning: "#e6db74",
info: "#66d9ef",
text: "#f8f8f2",
textDim: "#75715e",
textMuted: "#49483e",
background: "#272822",
backgroundPanel: "#1e1f1c",
backgroundElement: "#3e3d32",
border: "#49483e",
borderFocus: "#66d9ef",
borderWarning: "#e6db74",
borderModal: "#ae81ff",
bgHighlight: "#49483e",
bgCursor: "#66d9ef",
bgAdded: "#a6e22e",
bgRemoved: "#f92672",
diffAdded: "#a6e22e",
diffRemoved: "#f92672",
diffContext: "#75715e",
diffHeader: "#f8f8f2",
diffHunk: "#66d9ef",
roleUser: "#66d9ef",
roleAssistant: "#a6e22e",
roleSystem: "#e6db74",
roleTool: "#fd971f",
modeIdle: "#a6e22e",
modeEditing: "#66d9ef",
modeThinking: "#ae81ff",
modeToolExecution: "#e6db74",
modePermission: "#fd971f",
toolPending: "#75715e",
toolRunning: "#e6db74",
toolSuccess: "#a6e22e",
toolError: "#f92672",
headerGradient: ["#a6e22e", "#66d9ef", "#ae81ff"],
};
const CATPPUCCIN_COLORS: ThemeColors = {
primary: "#89b4fa",
secondary: "#74c7ec",
accent: "#cba6f7",
success: "#a6e3a1",
error: "#f38ba8",
warning: "#f9e2af",
info: "#89dceb",
text: "#cdd6f4",
textDim: "#6c7086",
textMuted: "#45475a",
background: "#1e1e2e",
backgroundPanel: "#181825",
backgroundElement: "#313244",
border: "#45475a",
borderFocus: "#89b4fa",
borderWarning: "#f9e2af",
borderModal: "#cba6f7",
bgHighlight: "#45475a",
bgCursor: "#89b4fa",
bgAdded: "#a6e3a1",
bgRemoved: "#f38ba8",
diffAdded: "#a6e3a1",
diffRemoved: "#f38ba8",
diffContext: "#6c7086",
diffHeader: "#cdd6f4",
diffHunk: "#89dceb",
roleUser: "#89dceb",
roleAssistant: "#a6e3a1",
roleSystem: "#f9e2af",
roleTool: "#fab387",
modeIdle: "#a6e3a1",
modeEditing: "#89b4fa",
modeThinking: "#cba6f7",
modeToolExecution: "#f9e2af",
modePermission: "#fab387",
toolPending: "#6c7086",
toolRunning: "#f9e2af",
toolSuccess: "#a6e3a1",
toolError: "#f38ba8",
headerGradient: ["#89b4fa", "#cba6f7", "#f5c2e7"],
};
const ONE_DARK_COLORS: ThemeColors = {
primary: "#61afef",
secondary: "#528bff",
accent: "#c678dd",
success: "#98c379",
error: "#e06c75",
warning: "#e5c07b",
info: "#56b6c2",
text: "#abb2bf",
textDim: "#5c6370",
textMuted: "#3e4451",
background: "#282c34",
backgroundPanel: "#21252b",
backgroundElement: "#2c323c",
border: "#3e4451",
borderFocus: "#61afef",
borderWarning: "#e5c07b",
borderModal: "#c678dd",
bgHighlight: "#3e4451",
bgCursor: "#61afef",
bgAdded: "#98c379",
bgRemoved: "#e06c75",
diffAdded: "#98c379",
diffRemoved: "#e06c75",
diffContext: "#5c6370",
diffHeader: "#abb2bf",
diffHunk: "#56b6c2",
roleUser: "#56b6c2",
roleAssistant: "#98c379",
roleSystem: "#e5c07b",
roleTool: "#d19a66",
modeIdle: "#98c379",
modeEditing: "#61afef",
modeThinking: "#c678dd",
modeToolExecution: "#e5c07b",
modePermission: "#d19a66",
toolPending: "#5c6370",
toolRunning: "#e5c07b",
toolSuccess: "#98c379",
toolError: "#e06c75",
headerGradient: ["#61afef", "#c678dd", "#56b6c2"],
};
const SOLARIZED_DARK_COLORS: ThemeColors = {
primary: "#268bd2",
secondary: "#2aa198",
accent: "#6c71c4",
success: "#859900",
error: "#dc322f",
warning: "#b58900",
info: "#2aa198",
text: "#839496",
textDim: "#586e75",
textMuted: "#073642",
background: "#002b36",
backgroundPanel: "#001f27",
backgroundElement: "#073642",
border: "#073642",
borderFocus: "#268bd2",
borderWarning: "#b58900",
borderModal: "#6c71c4",
bgHighlight: "#073642",
bgCursor: "#268bd2",
bgAdded: "#859900",
bgRemoved: "#dc322f",
diffAdded: "#859900",
diffRemoved: "#dc322f",
diffContext: "#586e75",
diffHeader: "#93a1a1",
diffHunk: "#2aa198",
roleUser: "#2aa198",
roleAssistant: "#859900",
roleSystem: "#b58900",
roleTool: "#cb4b16",
modeIdle: "#859900",
modeEditing: "#268bd2",
modeThinking: "#6c71c4",
modeToolExecution: "#b58900",
modePermission: "#cb4b16",
toolPending: "#586e75",
toolRunning: "#b58900",
toolSuccess: "#859900",
toolError: "#dc322f",
headerGradient: ["#268bd2", "#2aa198", "#859900"],
};
const GITHUB_DARK_COLORS: ThemeColors = {
primary: "#58a6ff",
secondary: "#388bfd",
accent: "#a371f7",
success: "#3fb950",
error: "#f85149",
warning: "#d29922",
info: "#58a6ff",
text: "#c9d1d9",
textDim: "#8b949e",
textMuted: "#484f58",
background: "#0d1117",
backgroundPanel: "#010409",
backgroundElement: "#161b22",
border: "#30363d",
borderFocus: "#58a6ff",
borderWarning: "#d29922",
borderModal: "#a371f7",
bgHighlight: "#161b22",
bgCursor: "#58a6ff",
bgAdded: "#238636",
bgRemoved: "#da3633",
diffAdded: "#3fb950",
diffRemoved: "#f85149",
diffContext: "#8b949e",
diffHeader: "#c9d1d9",
diffHunk: "#58a6ff",
roleUser: "#58a6ff",
roleAssistant: "#3fb950",
roleSystem: "#d29922",
roleTool: "#f0883e",
modeIdle: "#3fb950",
modeEditing: "#58a6ff",
modeThinking: "#a371f7",
modeToolExecution: "#d29922",
modePermission: "#f0883e",
toolPending: "#8b949e",
toolRunning: "#d29922",
toolSuccess: "#3fb950",
toolError: "#f85149",
headerGradient: ["#58a6ff", "#a371f7", "#3fb950"],
};
const ROSE_PINE_COLORS: ThemeColors = {
primary: "#c4a7e7",
secondary: "#9ccfd8",
accent: "#ebbcba",
success: "#31748f",
error: "#eb6f92",
warning: "#f6c177",
info: "#9ccfd8",
text: "#e0def4",
textDim: "#6e6a86",
textMuted: "#26233a",
background: "#191724",
backgroundPanel: "#1f1d2e",
backgroundElement: "#26233a",
border: "#26233a",
borderFocus: "#c4a7e7",
borderWarning: "#f6c177",
borderModal: "#ebbcba",
bgHighlight: "#21202e",
bgCursor: "#c4a7e7",
bgAdded: "#31748f",
bgRemoved: "#eb6f92",
diffAdded: "#31748f",
diffRemoved: "#eb6f92",
diffContext: "#6e6a86",
diffHeader: "#e0def4",
diffHunk: "#9ccfd8",
roleUser: "#9ccfd8",
roleAssistant: "#31748f",
roleSystem: "#f6c177",
roleTool: "#ebbcba",
modeIdle: "#31748f",
modeEditing: "#c4a7e7",
modeThinking: "#ebbcba",
modeToolExecution: "#f6c177",
modePermission: "#eb6f92",
toolPending: "#6e6a86",
toolRunning: "#f6c177",
toolSuccess: "#31748f",
toolError: "#eb6f92",
headerGradient: ["#c4a7e7", "#ebbcba", "#9ccfd8"],
};
const KANAGAWA_COLORS: ThemeColors = {
primary: "#7e9cd8",
secondary: "#7fb4ca",
accent: "#957fb8",
success: "#98bb6c",
error: "#c34043",
warning: "#dca561",
info: "#7fb4ca",
text: "#dcd7ba",
textDim: "#727169",
textMuted: "#363646",
background: "#1f1f28",
backgroundPanel: "#181820",
backgroundElement: "#2a2a37",
border: "#363646",
borderFocus: "#7e9cd8",
borderWarning: "#dca561",
borderModal: "#957fb8",
bgHighlight: "#2a2a37",
bgCursor: "#7e9cd8",
bgAdded: "#76946a",
bgRemoved: "#c34043",
diffAdded: "#98bb6c",
diffRemoved: "#c34043",
diffContext: "#727169",
diffHeader: "#dcd7ba",
diffHunk: "#7fb4ca",
roleUser: "#7fb4ca",
roleAssistant: "#98bb6c",
roleSystem: "#dca561",
roleTool: "#ffa066",
modeIdle: "#98bb6c",
modeEditing: "#7e9cd8",
modeThinking: "#957fb8",
modeToolExecution: "#dca561",
modePermission: "#ffa066",
toolPending: "#727169",
toolRunning: "#dca561",
toolSuccess: "#98bb6c",
toolError: "#c34043",
headerGradient: ["#7e9cd8", "#957fb8", "#7fb4ca"],
};
const AYU_DARK_COLORS: ThemeColors = {
primary: "#39bae6",
secondary: "#59c2ff",
accent: "#d2a6ff",
success: "#7fd962",
error: "#f07178",
warning: "#ffb454",
info: "#59c2ff",
text: "#bfbdb6",
textDim: "#636e78",
textMuted: "#232834",
background: "#0a0e14",
backgroundPanel: "#0d1016",
backgroundElement: "#1a1f29",
border: "#232834",
borderFocus: "#39bae6",
borderWarning: "#ffb454",
borderModal: "#d2a6ff",
bgHighlight: "#1a1f29",
bgCursor: "#39bae6",
bgAdded: "#7fd962",
bgRemoved: "#f07178",
diffAdded: "#7fd962",
diffRemoved: "#f07178",
diffContext: "#636e78",
diffHeader: "#bfbdb6",
diffHunk: "#59c2ff",
roleUser: "#59c2ff",
roleAssistant: "#7fd962",
roleSystem: "#ffb454",
roleTool: "#ff8f40",
modeIdle: "#7fd962",
modeEditing: "#39bae6",
modeThinking: "#d2a6ff",
modeToolExecution: "#ffb454",
modePermission: "#ff8f40",
toolPending: "#636e78",
toolRunning: "#ffb454",
toolSuccess: "#7fd962",
toolError: "#f07178",
headerGradient: ["#39bae6", "#d2a6ff", "#7fd962"],
};
const CARGDEV_CYBERPUNK_COLORS: ThemeColors = {
primary: "#8be9fd",
secondary: "#bd93f9",
accent: "#ff79c6",
success: "#50fa7b",
error: "#ff5555",
warning: "#ffb86c",
info: "#8be9fd",
text: "#e0e0e0",
textDim: "#666666",
textMuted: "#44475a",
background: "#0d1926",
backgroundPanel: "#071018",
backgroundElement: "#112233",
border: "#003b46",
borderFocus: "#8be9fd",
borderWarning: "#ffb86c",
borderModal: "#ff79c6",
bgHighlight: "#112233",
bgCursor: "#ff79c6",
bgAdded: "#50fa7b",
bgRemoved: "#ff5555",
diffAdded: "#50fa7b",
diffRemoved: "#ff5555",
diffContext: "#666666",
diffHeader: "#f8f8f2",
diffHunk: "#8be9fd",
roleUser: "#8be9fd",
roleAssistant: "#50fa7b",
roleSystem: "#ffb86c",
roleTool: "#bd93f9",
modeIdle: "#50fa7b",
modeEditing: "#8be9fd",
modeThinking: "#ff79c6",
modeToolExecution: "#ffb86c",
modePermission: "#bd93f9",
toolPending: "#666666",
toolRunning: "#ffb86c",
toolSuccess: "#50fa7b",
toolError: "#ff5555",
headerGradient: ["#ff79c6", "#bd93f9", "#8be9fd"],
};
export const THEMES: Record<string, Theme> = {
default: {
name: "default",
displayName: "Default",
colors: DEFAULT_COLORS,
},
dracula: {
name: "dracula",
displayName: "Dracula",
colors: DRACULA_COLORS,
},
nord: {
name: "nord",
displayName: "Nord",
colors: NORD_COLORS,
},
"tokyo-night": {
name: "tokyo-night",
displayName: "Tokyo Night",
colors: TOKYO_NIGHT_COLORS,
},
gruvbox: {
name: "gruvbox",
displayName: "Gruvbox",
colors: GRUVBOX_COLORS,
},
monokai: {
name: "monokai",
displayName: "Monokai",
colors: MONOKAI_COLORS,
},
catppuccin: {
name: "catppuccin",
displayName: "Catppuccin Mocha",
colors: CATPPUCCIN_COLORS,
},
"one-dark": {
name: "one-dark",
displayName: "One Dark",
colors: ONE_DARK_COLORS,
},
"solarized-dark": {
name: "solarized-dark",
displayName: "Solarized Dark",
colors: SOLARIZED_DARK_COLORS,
},
"github-dark": {
name: "github-dark",
displayName: "GitHub Dark",
colors: GITHUB_DARK_COLORS,
},
"rose-pine": {
name: "rose-pine",
displayName: "Rosé Pine",
colors: ROSE_PINE_COLORS,
},
kanagawa: {
name: "kanagawa",
displayName: "Kanagawa",
colors: KANAGAWA_COLORS,
},
"ayu-dark": {
name: "ayu-dark",
displayName: "Ayu Dark",
colors: AYU_DARK_COLORS,
},
"cargdev-cyberpunk": {
name: "cargdev-cyberpunk",
displayName: "Cargdev Cyberpunk",
colors: CARGDEV_CYBERPUNK_COLORS,
},
};
export const THEME_NAMES = Object.keys(THEMES);
export const DEFAULT_THEME = "default";
export const getTheme = (name: string): Theme => {
return THEMES[name] ?? THEMES[DEFAULT_THEME];
};
export const getThemeNames = (): string[] => {
return THEME_NAMES;
};

50
src/constants/tips.ts Normal file
View File

@@ -0,0 +1,50 @@
/**
* Tips and shortcuts constants for CodeTyper CLI
*/
// Tips with {highlight}text{/highlight} markers
export const TIPS = [
"Type {highlight}@filename{/highlight} to add a file to context",
"Use {highlight}/help{/highlight} to see all available commands",
"Press {highlight}/clear{/highlight} to start a fresh conversation",
"Use {highlight}--yes{/highlight} or {highlight}-y{/highlight} to auto-approve all commands",
"Add {highlight}--verbose{/highlight} to see detailed tool execution",
"Use {highlight}/models{/highlight} to see available models",
"Type {highlight}/provider{/highlight} to switch LLM providers",
"Files are automatically added to context with {highlight}@src/*.ts{/highlight} globs",
"Use {highlight}/save{/highlight} to save your session",
"Resume sessions with {highlight}-r{/highlight} or {highlight}--resume{/highlight}",
"Use {highlight}-c{/highlight} to continue your last session",
"Print mode {highlight}-p{/highlight} outputs response and exits",
"Permission patterns like {highlight}Bash(git:*){/highlight} auto-approve commands",
"Use {highlight}codetyper permissions ls{/highlight} to see allowed patterns",
"Add {highlight}Bash(npm install:*){/highlight} to allow npm install globally",
"Project settings are in {highlight}.codetyper/settings.json{/highlight}",
"Global settings are in {highlight}~/.codetyper/settings.json{/highlight}",
"Use {highlight}/context{/highlight} to check conversation size",
"Use {highlight}/compact{/highlight} to reduce context size",
"Commands like {highlight}ls{/highlight} and {highlight}cat{/highlight} are auto-approved",
"Use {highlight}/exit{/highlight} or {highlight}/quit{/highlight} to end the session",
"The agent can create folders with {highlight}mkdir{/highlight}",
"The agent can install packages with {highlight}npm install{/highlight}",
'Use {highlight}@"file with spaces"{/highlight} for files with spaces',
"The read tool shows file content with line numbers",
"The edit tool replaces exact text matches",
"The write tool creates files and directories",
"Use {highlight}/history{/highlight} to see past messages",
"Session rules persist until you exit",
"Project rules persist across sessions in this directory",
"Global rules apply everywhere",
] as const;
// Keyboard shortcuts
export const SHORTCUTS = [
{ key: "Ctrl+C", description: "Cancel current operation / Exit" },
{ key: "/help", description: "Show help" },
{ key: "/clear", description: "Clear conversation" },
{ key: "/exit", description: "Exit chat" },
{ key: "@file", description: "Add file to context" },
] as const;
// Highlight regex pattern
export const TIP_HIGHLIGHT_REGEX = /\{highlight\}(.*?)\{\/highlight\}/g;

13
src/constants/tools.ts Normal file
View File

@@ -0,0 +1,13 @@
/**
* Tool system constants
*/
export const SCHEMA_SKIP_KEYS = ["$schema"] as const;
export const SCHEMA_SKIP_VALUES: Record<string, unknown> = {
additionalProperties: false,
} as const;
export type SchemaSkipKey = (typeof SCHEMA_SKIP_KEYS)[number];
export const TOOL_NAMES = ["read", "glob", "grep"];

View File

@@ -0,0 +1,256 @@
/**
* TUI Component Constants
*
* Constants used by TUI components extracted for modularity
*/
import type { SelectOption, SlashCommand } from "@/types/tui";
// ============================================================================
// Header Constants
// ============================================================================
export const TUI_BANNER = [
"█▀▀ █▀█ █▀▄ █▀▀ ▀█▀ █▄█ █▀█ █▀▀ █▀█",
"█ █ █ █ █ █▀▀ █ █ █▀▀ █▀▀ █▀▄",
"▀▀▀ ▀▀▀ ▀▀ ▀▀▀ ▀ ▀ ▀ ▀▀▀ ▀ ▀",
] as const;
export const HEADER_GRADIENT_COLORS = [
"cyanBright",
"cyan",
"blueBright",
] as const;
export type HeaderGradientColor = (typeof HEADER_GRADIENT_COLORS)[number];
// ============================================================================
// Status Bar Mode Display Constants
// ============================================================================
export type ModeDisplayConfig = {
readonly text: string;
readonly color: "green" | "cyan" | "magenta" | "yellow";
};
export const MODE_DISPLAY_CONFIG: Record<string, ModeDisplayConfig> = {
idle: { text: "Ready", color: "green" },
editing: { text: "Editing", color: "cyan" },
thinking: { text: "✻ Thinking…", color: "magenta" },
tool_execution: { text: "✻ Running tool…", color: "yellow" },
permission_prompt: { text: "Awaiting permission", color: "yellow" },
command_menu: { text: "Command Menu", color: "cyan" },
model_select: { text: "Select Model", color: "magenta" },
agent_select: { text: "Select Agent", color: "magenta" },
theme_select: { text: "Select Theme", color: "magenta" },
mcp_select: { text: "MCP Servers", color: "magenta" },
mode_select: { text: "Select Mode", color: "magenta" },
provider_select: { text: "Select Provider", color: "magenta" },
learning_prompt: { text: "Save Learning?", color: "cyan" },
} as const;
export const DEFAULT_MODE_DISPLAY: ModeDisplayConfig = {
text: "Ready",
color: "green",
} as const;
// ============================================================================
// Log Panel Constants
// ============================================================================
export const LOG_ENTRY_EXTRA_LINES = {
user: 2,
assistant: 2,
tool: 1,
toolWithDiff: 10,
default: 1,
} as const;
export const TOOL_STATUS_ICONS = {
pending: "○",
running: "◐",
success: "✓",
error: "✗",
} as const;
export const TOOL_STATUS_COLORS = {
pending: "gray",
running: "yellow",
success: "green",
error: "red",
} as const;
export type ToolStatusColor =
(typeof TOOL_STATUS_COLORS)[keyof typeof TOOL_STATUS_COLORS];
export const THINKING_SPINNER_FRAMES = [
"⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏",
] as const;
export const THINKING_SPINNER_INTERVAL = 80;
export const LOG_PANEL_RESERVED_HEIGHT = 15;
export const LOG_PANEL_MIN_HEIGHT = 5;
export const LOG_PANEL_DEFAULT_TERMINAL_HEIGHT = 24;
// ============================================================================
// Permission Modal Constants
// ============================================================================
export const PERMISSION_OPTIONS: SelectOption[] = [
{
key: "y",
label: "Yes (once)",
value: "once",
description: "Allow this command only",
},
{
key: "s",
label: "Yes (session)",
value: "session",
description: "Allow pattern for this session",
},
{
key: "l",
label: "Yes (project)",
value: "local",
description: "Allow pattern for this project",
},
{
key: "g",
label: "Yes (global)",
value: "global",
description: "Allow pattern everywhere",
},
{
key: "n",
label: "No",
value: "deny",
description: "Deny this command",
},
];
export const PERMISSION_TYPE_LABELS = {
bash: "Execute command",
read: "Read file",
write: "Write file",
edit: "Edit file",
} as const;
// ============================================================================
// Command Menu Constants
// ============================================================================
export const SLASH_COMMANDS: SlashCommand[] = [
// General commands
{ name: "help", description: "Show available commands", category: "general" },
{
name: "clear",
description: "Clear conversation history",
category: "general",
},
{ name: "exit", description: "Exit the chat", category: "general" },
// Session commands
{ name: "save", description: "Save current session", category: "session" },
{
name: "context",
description: "Show context information",
category: "session",
},
{
name: "usage",
description: "Show token usage statistics",
category: "session",
},
{
name: "remember",
description: "Save a learning about the project",
category: "session",
},
{
name: "learnings",
description: "Show saved learnings",
category: "session",
},
// Settings commands
{ name: "model", description: "Select AI model", category: "settings" },
{ name: "agent", description: "Select agent", category: "settings" },
{ name: "mode", description: "Switch interaction mode", category: "settings" },
{
name: "provider",
description: "Switch LLM provider",
category: "settings",
},
{
name: "status",
description: "Show provider status",
category: "settings",
},
{ name: "theme", description: "Change color theme", category: "settings" },
{ name: "mcp", description: "Manage MCP servers", category: "settings" },
// Account commands
{
name: "whoami",
description: "Show logged in account",
category: "account",
},
{
name: "login",
description: "Authenticate with provider",
category: "account",
},
{
name: "logout",
description: "Sign out from provider",
category: "account",
},
];
export const COMMAND_CATEGORIES = [
"general",
"session",
"settings",
"account",
] as const;
export type CommandCategory = (typeof COMMAND_CATEGORIES)[number];
// ============================================================================
// Learning Modal Constants
// ============================================================================
export const LEARNING_OPTIONS: SelectOption[] = [
{
key: "y",
label: "Yes (project)",
value: "local",
description: "Save for this project",
},
{
key: "g",
label: "Yes (global)",
value: "global",
description: "Save for all projects",
},
{
key: "n",
label: "No",
value: "skip",
description: "Skip this learning",
},
];
export const LEARNING_CONTENT_MAX_LENGTH = 100;
export const LEARNING_TRUNCATION_SUFFIX = "...";

41
src/constants/ui.ts Normal file
View File

@@ -0,0 +1,41 @@
/**
* UI Constants
*/
// Keyboard hints displayed in status bar
export const STATUS_HINTS = {
INTERRUPT: "ctrl+c to interrupt",
INTERRUPT_CONFIRM: "ctrl+c again to confirm",
TOGGLE_TODOS: "ctrl+t to hide todos",
TOGGLE_TODOS_SHOW: "ctrl+t to show todos",
} as const;
// Time formatting
export const TIME_UNITS = {
SECOND: 1000,
MINUTE: 60 * 1000,
HOUR: 60 * 60 * 1000,
} as const;
// Token display formatting
export const TOKEN_DISPLAY = {
K_THRESHOLD: 1000,
DECIMALS: 1,
} as const;
// Status bar separator
export const STATUS_SEPARATOR = " · ";
// Interrupt timeout (ms) - time before interrupt pending resets
export const INTERRUPT_TIMEOUT = 2000;
// Terminal escape sequences for fullscreen mode
export const TERMINAL_SEQUENCES = {
ENTER_ALTERNATE_SCREEN: "\x1b[?1049h",
LEAVE_ALTERNATE_SCREEN: "\x1b[?1049l",
CLEAR_SCREEN: "\x1b[2J",
CLEAR_SCROLLBACK: "\x1b[3J",
CURSOR_HOME: "\x1b[H",
HIDE_CURSOR: "\x1b[?25l",
SHOW_CURSOR: "\x1b[?25h",
} as const;

11
src/constants/view.ts Normal file
View File

@@ -0,0 +1,11 @@
/**
* View tool constants
*/
export const VIEW_MESSAGES = {
FAILED: (error: unknown) => `Failed to read file: ${error}`,
} as const;
export const VIEW_DEFAULTS = {
START_LINE: 1,
} as const;

24
src/constants/write.ts Normal file
View File

@@ -0,0 +1,24 @@
/**
* Write tool constants
*/
export const WRITE_MESSAGES = {
PERMISSION_DENIED: "Permission denied by user",
} as const;
export const WRITE_TITLES = {
CANCELLED: (path: string) => `Write cancelled: ${path}`,
FAILED: (path: string) => `Write failed: ${path}`,
WRITING: (name: string) => `Writing ${name}`,
OVERWROTE: (path: string) => `Overwrote: ${path}`,
CREATED: (path: string) => `Created: ${path}`,
OVERWRITE_DESC: (path: string) => `Overwrite file: ${path}`,
CREATE_DESC: (path: string) => `Create file: ${path}`,
} as const;
export const WRITE_DESCRIPTION = `Write content to a file. Creates the file if it doesn't exist, or overwrites if it does.
Guidelines:
- Use absolute paths
- Parent directories will be created automatically
- Requires user approval for file writes`;