@kishlay42/moth-ai 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ // Palette mapping: Character -> { color: Hex, symbol: Char }
4
+ const PALETTE = {
5
+ // Deep Blue / Background blend
6
+ '1': { color: '#0f20a2', char: '░' },
7
+ // Outer Blue
8
+ '2': { color: '#0020ef', char: '▒' },
9
+ // Mid Blue
10
+ '3': { color: '#002dff', char: '▓' },
11
+ // Bright Blue
12
+ '4': { color: '#0088af', char: '█' },
13
+ // Cyan
14
+ '5': { color: '#00c7f7', char: '█' },
15
+ // Light Cyan
16
+ '6': { color: '#00ffff', char: '█' },
17
+ // White Core
18
+ '7': { color: '#ffffff', char: '█' },
19
+ };
20
+ const HD_FLAME_ART = [
21
+ " 333 ",
22
+ " 2333332 ",
23
+ " 233444332 ",
24
+ " 2334555554332 ",
25
+ " 234556666655432 ",
26
+ " 34567777777776543 ",
27
+ " 2456777777777776542 ",
28
+ " 235677766666667776532 ",
29
+ " 345677655555556776543 ",
30
+ " 23456765444444456765432 ",
31
+ " 23455654333333345655432 ",
32
+ " 12344543222222234544321 ",
33
+ " 11233432111111123433211 "
34
+ ];
35
+ export const FlameLogo = () => {
36
+ return (_jsx(Box, { flexDirection: "column", marginRight: 2, children: HD_FLAME_ART.map((row, rowIndex) => (_jsx(Box, { children: row.split('').map((char, colIndex) => {
37
+ const pixel = PALETTE[char];
38
+ return (_jsx(Text, { color: pixel?.color || undefined, children: pixel ? pixel.char : ' ' }, `${rowIndex}-${colIndex}`));
39
+ }) }, rowIndex))) }));
40
+ };
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import BigText from 'ink-big-text';
4
+ export const WordFlame = ({ text, big = false }) => {
5
+ if (big) {
6
+ return (_jsx(Text, { color: "#3EA0C3", children: _jsx(BigText, { text: text, font: "block", colors: ['#0192e5', '#0192e5'] }) }) // BigText needs specific handling or Text wrapper
7
+ );
8
+ }
9
+ return (_jsx(Text, { color: "#0192e5", children: text }));
10
+ };
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import BigText from 'ink-big-text';
4
+ export const WordMoth = ({ text, big = false }) => {
5
+ if (big) {
6
+ return (_jsx(Text, { color: "#3EA0C3", children: _jsx(BigText, { text: text, font: "block", colors: ['#0192e5', '#0192e5'] }) }) // BigText needs specific handling or Text wrapper
7
+ );
8
+ }
9
+ return (_jsx(Text, { color: "#0192e5", children: text }));
10
+ };
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput, Newline, useApp } from 'ink';
4
+ import { removeProfile, saveConfig } from '../../config/configManager.js';
5
+ import { deleteApiKey } from '../../config/keychain.js';
6
+ export const LLMRemover = ({ config: initialConfig, onExit }) => {
7
+ const { exit } = useApp();
8
+ const [localConfig, setLocalConfig] = useState(initialConfig);
9
+ const [selectionIndex, setSelectionIndex] = useState(0);
10
+ const [message, setMessage] = useState('');
11
+ const [confirming, setConfirming] = useState(null);
12
+ useInput(async (input, key) => {
13
+ // Escape or Ctrl+C/X to exit
14
+ if (key.escape || (key.ctrl && (input === 'c' || input === 'x'))) {
15
+ onExit();
16
+ return;
17
+ }
18
+ if (localConfig.profiles.length === 0) {
19
+ return;
20
+ }
21
+ // Navigation
22
+ if (key.upArrow) {
23
+ setSelectionIndex(prev => (prev - 1 + localConfig.profiles.length) % localConfig.profiles.length);
24
+ return;
25
+ }
26
+ if (key.downArrow) {
27
+ setSelectionIndex(prev => (prev + 1) % localConfig.profiles.length);
28
+ return;
29
+ }
30
+ const selectedProfile = localConfig.profiles[selectionIndex];
31
+ // Confirmation Mode
32
+ if (confirming) {
33
+ if (key.return || input.toLowerCase() === 'y') {
34
+ // Do the delete
35
+ const nameToRemove = selectedProfile.name;
36
+ let newConfig = removeProfile(localConfig, nameToRemove);
37
+ await deleteApiKey(nameToRemove); // Helper needs to be imported or mocked if not in configManager? It's in keychain.ts actually.
38
+ // Wait, removeProfile in configManager does cleaner logic but key deletion is separate usually?
39
+ // Checking imports... "deleteApiKey" is in keychain.ts.
40
+ // I need to import it.
41
+ saveConfig(newConfig);
42
+ setLocalConfig(newConfig);
43
+ setConfirming(null);
44
+ setMessage(`Profile '${nameToRemove}' removed.`);
45
+ setSelectionIndex(0);
46
+ setTimeout(() => setMessage(''), 3000);
47
+ }
48
+ else if (input.toLowerCase() === 'n' || key.escape) {
49
+ setConfirming(null);
50
+ }
51
+ return;
52
+ }
53
+ // Trigger Delete
54
+ if (key.return || key.delete || key.backspace) {
55
+ setConfirming(selectedProfile.name);
56
+ }
57
+ });
58
+ // Need to fix imports for deleteApiKey which is in keychain, not configManager.
59
+ // I will assume I can import it from '../../config/keychain.js'.
60
+ if (localConfig.profiles.length === 0) {
61
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "red", children: [_jsx(Text, { color: "red", children: "No profiles found." }), _jsx(Text, { children: "Use \"moth llm add\" to create one." })] }));
62
+ }
63
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "red", children: [_jsx(Text, { bold: true, color: "red", children: "MOTH LLM REMOVER" }), _jsx(Text, { color: "yellow", children: "Hint: Select profile and press Enter to delete." }), _jsx(Newline, {}), confirming ? (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "red", padding: 1, children: [_jsx(Text, { bold: true, color: "white", backgroundColor: "red", children: " WARNING " }), _jsx(Text, { children: "Are you sure you want to permanently delete profile:" }), _jsxs(Text, { bold: true, children: ["'", confirming, "'?"] }), _jsx(Newline, {}), _jsx(Text, { bold: true, children: "[Y] Yes, Delete [N] No, Cancel" })] })) : (localConfig.profiles.map((p, i) => {
64
+ const isSelected = i === selectionIndex;
65
+ const isActive = p.name === localConfig.activeProfile;
66
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: isSelected ? "red" : undefined, bold: isSelected, children: isSelected ? "❯ " : " " }), _jsx(Text, { bold: isActive, color: isActive ? "green" : (isSelected ? "red" : undefined), children: p.name }), _jsxs(Text, { children: [" (", p.provider, " / ", p.model, ")"] }), isActive && _jsx(Text, { color: "green", dimColor: true, children: " (active)" })] }, i));
67
+ })), message && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: message }) }))] }));
68
+ };
@@ -0,0 +1,149 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import SelectInput from 'ink-select-input';
5
+ import TextInput from 'ink-text-input';
6
+ // 1. Model Families
7
+ const FAMILIES = [
8
+ { label: 'GPT (OpenAI)', value: 'gpt' },
9
+ { label: 'Claude (Anthropic)', value: 'claude' },
10
+ { label: 'Gemini (Google)', value: 'gemini' },
11
+ { label: 'LLaMA (Meta)', value: 'llama' },
12
+ { label: 'Mistral', value: 'mistral' },
13
+ { label: 'Qwen', value: 'qwen' },
14
+ { label: 'Phi', value: 'phi' },
15
+ { label: 'DeepSeek', value: 'deepseek' },
16
+ { label: 'Other / Custom', value: 'custom' },
17
+ ];
18
+ // 2. Deployment Types
19
+ const DEPLOYMENTS = [
20
+ { label: 'Local (On my machine)', value: 'local' },
21
+ { label: 'Cloud (Hosted API)', value: 'cloud' },
22
+ ];
23
+ // Suggested models per family
24
+ const FAMILY_MODELS = {
25
+ 'gpt': ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'],
26
+ 'claude': ['claude-3-5-sonnet-20240620', 'claude-3-opus-20240229', 'claude-3-haiku-20240307'],
27
+ 'gemini': ['gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-1.0-pro'],
28
+ 'llama': ['llama3.1', 'llama3', 'llama2'],
29
+ 'mistral': ['mistral', 'mixtral', 'codestral'],
30
+ 'qwen': ['qwen2.5', 'qwen2'],
31
+ 'phi': ['phi3.5', 'phi3'],
32
+ 'deepseek': ['deepseek-coder-v2', 'deepseek-chat'],
33
+ };
34
+ export const LLMWizard = ({ onComplete, onCancel }) => {
35
+ const [step, setStep] = useState(1);
36
+ const [family, setFamily] = useState('');
37
+ const [deployment, setDeployment] = useState('');
38
+ const [model, setModel] = useState('');
39
+ const [isCustomModel, setIsCustomModel] = useState(false);
40
+ const [apiKey, setApiKey] = useState('');
41
+ const [baseUrl, setBaseUrl] = useState('');
42
+ // Resolution State
43
+ const [resolvedRuntime, setResolvedRuntime] = useState({ provider: 'openai-compatible', requiresKey: true, requiresUrl: false });
44
+ // --- STEP 1: FAMILY ---
45
+ const handleFamilySelect = (item) => {
46
+ setFamily(item.value);
47
+ setStep(2);
48
+ };
49
+ // --- STEP 2: DEPLOYMENT ---
50
+ const handleDeploymentSelect = (item) => {
51
+ const deployType = item.value;
52
+ setDeployment(deployType);
53
+ resolveRuntime(family, deployType);
54
+ };
55
+ const resolveRuntime = (fam, deploy) => {
56
+ // LOGIC RESOLUTION
57
+ if (deploy === 'local') {
58
+ setResolvedRuntime({
59
+ provider: 'ollama',
60
+ defaultBaseUrl: 'http://localhost:11434/v1',
61
+ requiresKey: false,
62
+ requiresUrl: false // Implicitly handles URL usually, or we can confirm it
63
+ });
64
+ }
65
+ else {
66
+ // CLOUD
67
+ if (fam === 'gemini') {
68
+ setResolvedRuntime({ provider: 'gemini-native', requiresKey: true, requiresUrl: false });
69
+ }
70
+ else if (fam === 'claude') {
71
+ setResolvedRuntime({ provider: 'claude-native', requiresKey: true, requiresUrl: false });
72
+ }
73
+ else if (fam === 'gpt') {
74
+ // OpenAI standard
75
+ setResolvedRuntime({ provider: 'openai-compatible', requiresKey: true, requiresUrl: false });
76
+ }
77
+ else {
78
+ // LLaMA, Mistral, etc on Cloud -> Generic OpenAI Compatible (Groq, etc)
79
+ setResolvedRuntime({ provider: 'openai-compatible', requiresKey: true, requiresUrl: true });
80
+ }
81
+ }
82
+ setStep(3);
83
+ };
84
+ // --- STEP 3: MODEL VARIANT ---
85
+ const handleModelSelect = (item) => {
86
+ if (item.value === 'custom') {
87
+ setIsCustomModel(true);
88
+ setModel('');
89
+ }
90
+ else {
91
+ setModel(item.value);
92
+ setStep(4);
93
+ }
94
+ };
95
+ const handleCustomModelSubmit = (val) => {
96
+ setModel(val);
97
+ setStep(4);
98
+ };
99
+ // --- STEP 4: AUTH / CREDENTIALS ---
100
+ // This step might be split if we need both Key and URL.
101
+ // Let's do Key first, then URL if needed.
102
+ const [subStep, setSubStep] = useState('key');
103
+ useEffect(() => {
104
+ if (step === 4) {
105
+ if (resolvedRuntime.requiresKey) {
106
+ setSubStep('key');
107
+ }
108
+ else if (resolvedRuntime.requiresUrl) {
109
+ setSubStep('url');
110
+ }
111
+ else {
112
+ // Nothing needed
113
+ setStep(5);
114
+ }
115
+ }
116
+ }, [step, resolvedRuntime]);
117
+ const handleKeySubmit = (val) => {
118
+ setApiKey(val);
119
+ if (resolvedRuntime.requiresUrl) {
120
+ setSubStep('url');
121
+ }
122
+ else {
123
+ setStep(5);
124
+ }
125
+ };
126
+ const handleUrlSubmit = (val) => {
127
+ setBaseUrl(val);
128
+ setStep(5);
129
+ };
130
+ // --- STEP 5: CONFIRM ---
131
+ useInput((input, key) => {
132
+ if (step === 5 && key.return) {
133
+ onComplete({
134
+ name: `${family}-${model}`.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase(),
135
+ provider: resolvedRuntime.provider,
136
+ model: model,
137
+ apiKey: apiKey || undefined,
138
+ baseUrl: baseUrl || resolvedRuntime.defaultBaseUrl || undefined
139
+ });
140
+ }
141
+ if (input === 'c' && key.ctrl) {
142
+ onCancel();
143
+ }
144
+ });
145
+ // RENDER HELPERS
146
+ const modelItems = (FAMILY_MODELS[family] || []).map(m => ({ label: m, value: m }));
147
+ modelItems.push({ label: 'Type Custom Model Name...', value: 'custom' });
148
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "magenta", width: 65, children: [_jsx(Text, { bold: true, color: "magenta", children: "\uD83D\uDD25 MOTH MODEL WIZARD" }), _jsx(Text, { color: "gray", children: "--------------------------------------------------" }), step === 1 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "STEP 1: SELECT MODEL FAMILY" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: FAMILIES, onSelect: handleFamilySelect }) })] })), step === 2 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "STEP 2: SELECT DEPLOYMENT" }), _jsxs(Text, { color: "gray", children: ["Where will ", family, " run?"] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: DEPLOYMENTS, onSelect: handleDeploymentSelect }) })] })), step === 3 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "STEP 3: SELECT MODEL VARIANT" }), _jsxs(Text, { color: "gray", children: ["Runtime: ", resolvedRuntime.provider, " (", deployment, ")"] }), isCustomModel ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Enter specific model name:" }), _jsx(TextInput, { value: model, onChange: setModel, onSubmit: handleCustomModelSubmit })] })) : (_jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: modelItems, onSelect: handleModelSelect }) }))] })), step === 4 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "STEP 4: CREDENTIALS" }), subStep === 'key' && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["Enter API Key for ", family, " (", deployment, "):"] }), _jsx(TextInput, { value: apiKey, onChange: setApiKey, onSubmit: handleKeySubmit, mask: "*" })] })), subStep === 'url' && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Enter Base URL endpoint:" }), _jsx(TextInput, { value: baseUrl, onChange: setBaseUrl, onSubmit: handleUrlSubmit, placeholder: "https://api.groq.com/openai/v1" })] }))] })), step === 5 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "green", children: "STEP 5: CONFIRM RESOLUTION" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: "single", padding: 1, borderColor: "white", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Family:" }), " ", family] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Deployment:" }), " ", deployment] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Runtime:" }), " ", resolvedRuntime.provider] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Model:" }), " ", model] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Auth:" }), " ", apiKey ? '(Provided)' : '(None)'] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Endpoint:" }), " ", baseUrl || resolvedRuntime.defaultBaseUrl || '(Default)'] })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", bold: true, children: "Press [ENTER] to save configuration." }), _jsx(Text, { color: "red", children: "Press [Ctrl+C] to cancel." })] })] }))] }));
149
+ };
@@ -0,0 +1,22 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ export const CONFIG_DIR = path.join(os.homedir(), '.moth');
5
+ export function ensureConfigDir() {
6
+ if (!fs.existsSync(CONFIG_DIR)) {
7
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
8
+ }
9
+ }
10
+ export function findProjectRoot(cwd = process.cwd()) {
11
+ let currentDir = cwd;
12
+ while (true) {
13
+ if (fs.existsSync(path.join(currentDir, 'package.json')) || fs.existsSync(path.join(currentDir, '.git'))) {
14
+ return currentDir;
15
+ }
16
+ const parentDir = path.dirname(currentDir);
17
+ if (parentDir === currentDir) {
18
+ return null;
19
+ }
20
+ currentDir = parentDir;
21
+ }
22
+ }
@@ -0,0 +1,49 @@
1
+ export function levenshtein(a, b) {
2
+ if (a.length === 0)
3
+ return b.length;
4
+ if (b.length === 0)
5
+ return a.length;
6
+ const matrix = [];
7
+ // increment along the first column of each row
8
+ let i;
9
+ for (i = 0; i <= b.length; i++) {
10
+ matrix[i] = [i];
11
+ }
12
+ // increment each column in the first row
13
+ let j;
14
+ for (j = 0; j <= a.length; j++) {
15
+ matrix[0][j] = j;
16
+ }
17
+ // Fill in the rest of the matrix
18
+ for (i = 1; i <= b.length; i++) {
19
+ for (j = 1; j <= a.length; j++) {
20
+ if (b.charAt(i - 1) == a.charAt(j - 1)) {
21
+ matrix[i][j] = matrix[i - 1][j - 1];
22
+ }
23
+ else {
24
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
25
+ Math.min(matrix[i][j - 1] + 1, // insertion
26
+ matrix[i - 1][j] + 1 // deletion
27
+ ));
28
+ }
29
+ }
30
+ }
31
+ return matrix[b.length][a.length];
32
+ }
33
+ export function findClosestCommand(input, commands) {
34
+ let closest = null;
35
+ let minDistance = Infinity;
36
+ for (const cmd of commands) {
37
+ const distance = levenshtein(input, cmd);
38
+ if (distance < minDistance) {
39
+ minDistance = distance;
40
+ closest = cmd;
41
+ }
42
+ }
43
+ // Threshold: allow max distance of 2 for words len > 3, else 1
44
+ const threshold = input.length > 3 ? 2 : 1;
45
+ if (minDistance <= threshold) {
46
+ return closest;
47
+ }
48
+ return null;
49
+ }
@@ -0,0 +1,63 @@
1
+ # System Architecture
2
+
3
+ ## Overview
4
+ Moth is a local, LLM-agnostic code intelligence CLI. It acts as an orchestrator between the user, the filesystem, and various LLM providers.
5
+
6
+ ## High-Level Architecture
7
+
8
+ ```mermaid
9
+ graph TD
10
+ User[User] -->|Commands/Prompts| CLI[CLI Entry Point (index.ts)]
11
+ CLI --> UI[UI Layer (Ink)]
12
+ CLI --> Config[Config Manager]
13
+ UI --> App[App Component]
14
+ App --> Orch[Agent Orchestrator]
15
+ Orch --> Tools[Tool Registry]
16
+ Orch --> LLM[LLM Client]
17
+
18
+ subgraph Core Logic
19
+ Orch
20
+ Tools
21
+ end
22
+
23
+ subgraph Infrastructure
24
+ Config
25
+ LLM
26
+ FS[File System]
27
+ end
28
+
29
+ Tools --> FS
30
+ LLM -->|API Calls| Cloud[Cloud Providers (OpenAI/Gemini/Claude)]
31
+ LLM -->|Local| Local[Ollama/LocalHost]
32
+ ```
33
+
34
+ ## Component Interaction
35
+
36
+ ### Chat Session Flow
37
+ 1. **Initialization**: `startChatSession()` in `index.ts` loads configuration and mounts the `App` component.
38
+ 2. **Input**: User types a prompt.
39
+ 3. **App State**: `App.tsx` captures the input and appends it to the `messages` history.
40
+ 4. **Orchestration**: `App.tsx` calls `orchestrator.run(prompt, history)`.
41
+ 5. **Agent Loop**:
42
+ * **System Prompt**: Constructed with available tools.
43
+ * **LLM Call**: History + System Prompt + User Prompt sent to LLM.
44
+ * **Reasoning**: LLM returns a JSON `AgentStep` (Thought + ToolCall/FinalAnswer).
45
+ * **Execution**: If ToolCall, `ToolRegistry` executes the tool (e.g., `write_to_file`).
46
+ * **Recursion**: Output is fed back to LLM until `finalAnswer` is reached.
47
+ 6. **Render**: `App.tsx` updates the UI with the stream or final result.
48
+
49
+ ### LLM Provider Resolution
50
+ The system uses a **Model-First** pattern to resolve providers:
51
+
52
+ ```mermaid
53
+ sequenceDiagram
54
+ participant User
55
+ participant Wizard
56
+ participant Config
57
+
58
+ User->>Wizard: Select Family (e.g. LLaMA)
59
+ User->>Wizard: Select Deployment (Local)
60
+ Wizard->>Config: Resolve to 'ollama' provider
61
+ Wizard->>Config: Set URL to 'http://127.0.0.1:11434'
62
+ Wizard->>User: Confirm "Runtime: Ollama"
63
+ ```
@@ -0,0 +1,53 @@
1
+ # Core Logic Modules
2
+
3
+ ## Agent Orchestrator (`src/agent/orchestrator.ts`)
4
+
5
+ The `AgentOrchestrator` is the brain of Moth. It implements a ReAct-style loop to solve user tasks.
6
+
7
+ ### Key Responsibilities
8
+ - **Conversation History**: It maintains `messages` state (User/Assistant), providing short-term memory.
9
+ - **Tool Execution**: It parses LLM responses and executes tools via `ToolRegistry`.
10
+ - **Loop Management**: It manages the `maxSteps` loop to prevent infinite recursion.
11
+
12
+ ### Code Snippet: Memory Injection
13
+ ```typescript
14
+ // src/agent/orchestrator.ts
15
+ async *run(prompt: string, history: LLMMessage[] = []): AsyncGenerator<AgentStep, string, unknown> {
16
+ // ...
17
+ const messages: LLMMessage[] = [
18
+ { role: 'user', content: systemPrompt },
19
+ ...history, // <--- Injection of past context
20
+ { role: 'user', content: currentPrompt }
21
+ ];
22
+ // ...
23
+ }
24
+ ```
25
+
26
+ ## Context Management (`src/context`)
27
+
28
+ Moth uses a proactive context system to understand the user's project.
29
+
30
+ ### Project Scanner (`src/context/scanner.ts`)
31
+ - **Function**: Scans the current directory to identify project types (Node.js, Python, C++, etc.).
32
+ - **Heuristics**: Checks for marker files (`package.json`, `requirements.txt`, `CMakeLists.txt`).
33
+
34
+ ## Configuration Manager (`src/config/configManager.ts`)
35
+
36
+ The config manager handles persistent state, including LLM profiles.
37
+
38
+ ### Data Structure
39
+ ```typescript
40
+ interface ActiveConfig {
41
+ activeProfile: string;
42
+ profiles: Record<string, LLMProfile>;
43
+ }
44
+
45
+ interface LLMProfile {
46
+ name: string;
47
+ provider: 'ollama' | 'openai-compatible' | 'gemini-native' | ...;
48
+ model: string;
49
+ apiBase?: string;
50
+ apiKeyLoc?: string; // Pointer to keychain, not the key itself
51
+ }
52
+ ```
53
+ - **Security**: API keys are NEVER stored in `config.json`. They are managed via the system keychain (or mock implementation).
package/docs/index.md ADDED
@@ -0,0 +1,30 @@
1
+ # Moth Documentation 📜
2
+
3
+ Welcome to the comprehensive documentation for Moth, the Model-First Code Intelligence CLI.
4
+
5
+ ## Table of Contents
6
+
7
+ ### 1. [System Architecture](./architecture.md)
8
+ * High-Level Overview
9
+ * Mermaid Diagrams (System & Workflows)
10
+ * Interaction Flows
11
+
12
+ ### 2. [Core Logic](./core_logic.md)
13
+ * **Orchestrator**: The ReAct Agent Brain.
14
+ * **Context**: How Moth analyzes your project.
15
+ * **Config**: State management and security.
16
+
17
+ ### 3. [UI Components](./ui_components.md)
18
+ * **Ink Architecture**: React-based TUI.
19
+ * **Wizards**: Interactive setup flows.
20
+ * **Components**: Reusable UI elements.
21
+
22
+ ### 4. [LLM Integration](./llm_integration.md)
23
+ * **Factory Pattern**: Dynamic client creation.
24
+ * **Adapters**: OpenAI, Gemini, Claude, Ollama.
25
+ * **Model-First Design**: Resolution logic.
26
+
27
+ ## Quick Links
28
+
29
+ * [Implementation Plan](../.gemini/antigravity/brain/0989b9c8-1bda-4a68-b9de-08e5d9c71f0f/implementation_plan.md) (Internal Planning)
30
+ * [Walkthrough](../.gemini/antigravity/brain/0989b9c8-1bda-4a68-b9de-08e5d9c71f0f/walkthrough.md) (User Guide)
@@ -0,0 +1,49 @@
1
+ # LLM Integration (`src/llm`)
2
+
3
+ Moth routes all model interactions through a unified adapter layer.
4
+
5
+ ## Factory Pattern (`src/llm/factory.ts`)
6
+
7
+ The `createLLMClient` function instantiates the correct adapter based on the configuration.
8
+
9
+ ```typescript
10
+ export const createLLMClient = (config: ActiveConfig): LLMClient => {
11
+ switch (config.provider) {
12
+ case 'ollama':
13
+ case 'openai-compatible':
14
+ return new OpenAIAdapter(...);
15
+ case 'gemini-native':
16
+ return new GeminiAdapter(...);
17
+ case 'claude-native':
18
+ return new ClaudeAdapter(...);
19
+ // ...
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## Adapter Interface (`src/llm/types.ts`)
25
+
26
+ All adapters must implement the `LLMClient` interface.
27
+
28
+ ```typescript
29
+ export interface LLMClient {
30
+ chat(messages: LLMMessage[]): Promise<string>;
31
+ chatStream(messages: LLMMessage[]): AsyncGenerator<string, void, unknown>;
32
+ }
33
+ ```
34
+
35
+ ## Supported Providers
36
+
37
+ ### OpenAI Compatible (`src/llm/openAIAdapter.ts`)
38
+ - **Usage**: Used for OpenAI, Ollama, vLLM, DeepSeek, generic endpoints.
39
+ - **Configuration**: Requires `apiBase` (URL) and optionally `apiKey`.
40
+ - **Ollama Special Case**: Uses `http://127.0.0.1:11434/v1` as default base.
41
+
42
+ ### Gemini Native (`src/llm/geminiAdapter.ts`)
43
+ - **Usage**: Google Gemini API.
44
+ - **Library**: Uses `@google/generative-ai` SDK.
45
+ - **Safety**: Configures safety settings to BLOCK_NONE for coding tasks.
46
+
47
+ ### Claude Native (`src/llm/claudeAdapter.ts`)
48
+ - **Usage**: Anthropic Claude API.
49
+ - **Library**: Uses `@anthropic-ai/sdk`.
@@ -0,0 +1,44 @@
1
+ # UI Components (`src/ui`)
2
+
3
+ Moth uses [React Ink](https://github.com/vadimdemedes/ink) to render a Terminal User Interface (TUI).
4
+
5
+ ## Main Application (`src/ui/App.tsx`)
6
+
7
+ The `App` component acts as the main view controller.
8
+
9
+ ### Features
10
+ - **Chat Interface**: Renders a scrollable list of messages using `flexDirection="column"`.
11
+ - **Input Handling**: Captures user keystrokes for chat and control signals (Pause/Resume).
12
+ - **Permission Queue**: Intercepts tool execution requests and displays a "Permission Required" overlay.
13
+
14
+ ```tsx
15
+ // Structure of the App component
16
+ <Box flexDirection="column">
17
+ <Header />
18
+ <MessageList messages={messages} />
19
+ {pendingPermission && <PermissionOverlay request={pendingPermission} />}
20
+ <InputArea isProcessing={isProcessing} />
21
+ </Box>
22
+ ```
23
+
24
+ ## Wizards
25
+
26
+ Wizards are self-contained interactive components used for configuration.
27
+
28
+ ### LLM Add Wizard (`src/ui/wizards/LLMWizard.tsx`)
29
+ Implements the **Model-First** setup flow.
30
+ 1. **Family Selection**: Selects model family (LLaMA, GPT, etc.).
31
+ 2. **Deployment Selection**: Local vs Cloud.
32
+ 3. **Credential Entry**: Conditional rendering of input fields.
33
+ 4. **Confirmation**: Final summary view.
34
+
35
+ ### LLM Remove Wizard (`src/ui/wizards/LLMRemover.tsx`)
36
+ - Displays a list of profiles.
37
+ - Uses styled selection (Green for active, Red for deletion target).
38
+ - Requires explicit 'y' confirmation to prevent accidents.
39
+
40
+ ## Utilities
41
+
42
+ ### WordMoth (`src/ui/components/WordMoth.tsx`)
43
+ - Renders the "MOTH" ascii art header.
44
+ - Supports gradient coloring (Blue/Cyan theme).