local-agent
Version:
A CLI agentic system for orchestrating tools and memory with per-folder scoping
143 lines • 6.49 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useCallback, useEffect } from "react";
import { Box, Text } from "ink";
import TextInput from "ink-text-input";
import Spinner from "ink-spinner";
import { validateAndLoadFiles, loadAllMcpTools } from "./initialization.js";
import { marked } from "marked";
import TerminalRenderer from "marked-terminal";
// MarkdownRenderer using marked + marked-terminal (CJS compatible)
const MarkdownRenderer = (props) => {
const ansi = marked(props.children, { renderer: new TerminalRenderer() });
return _jsx(Text, { children: ansi });
};
import { createAISDKTools } from "@agentic/ai-sdk";
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
import { google } from "@ai-sdk/google";
import { generateText } from "ai";
const REQUIRED_FILES = ["local-agent.json", "mcp-tools.json", "keys.json", "system.md"];
const MEMORY_DIR = "memory";
// Model selection logic from interactions.ts
function parseModelString(modelString) {
if (!modelString.includes("/")) {
return { provider: "openai", modelName: modelString };
}
const [provider, ...rest] = modelString.split("/");
return { provider: provider.toLowerCase(), modelName: rest.join("/") };
}
function getClientForProvider(provider) {
switch (provider) {
case "openai":
return openai;
case "anthropic":
return anthropic;
case "google":
return google;
default:
throw new Error(`Unsupported AI provider: ${provider}`);
}
}
const App = () => {
const [input, setInput] = useState("");
const [conversation, setConversation] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [config, setConfig] = useState(null);
const [tools, setTools] = useState(null);
const [keys, setKeys] = useState(null);
const [loadedTools, setLoadedTools] = useState(null);
const agentName = config?.name || "Local Agent";
// Load config, tools, and keys on mount
useEffect(() => {
(async () => {
try {
const { config, tools, keys } = await validateAndLoadFiles(REQUIRED_FILES, MEMORY_DIR);
setConfig(config);
setTools(tools);
setKeys(keys);
const { loadedTools } = await loadAllMcpTools(tools);
setLoadedTools(loadedTools);
setIsLoading(false);
}
catch (e) {
setError(e.message || "Failed to load configuration or tools.");
setIsLoading(false);
}
})();
}, []);
// Build conversation context string (last 10 messages)
const buildConversationContext = () => {
if (conversation.length === 0)
return "";
const recent = conversation.slice(-10);
let context = "\n\nCONVERSATION HISTORY:\n";
recent.forEach((msg) => {
const timeStr = msg.timestamp.toLocaleTimeString();
context += `[${timeStr}] ${msg.role.toUpperCase()}: ${msg.content}\n`;
if (msg.toolUsed) {
context += `[${timeStr}] TOOL_USED: ${msg.toolUsed}\n`;
}
});
return context;
};
const handleSubmit = useCallback(async (value) => {
setError(null);
setIsLoading(true);
const userMsg = {
role: "user",
content: value,
timestamp: new Date()
};
setConversation((prev) => [...prev, userMsg]);
setInput("");
try {
// Prepare tools for LLM
const mcpToolInstances = loadedTools ? Object.values(loadedTools) : [];
const allTools = mcpToolInstances.length === 1
? createAISDKTools(mcpToolInstances[0])
: createAISDKTools(...mcpToolInstances);
// Build prompt with context
const contextualPrompt = value + buildConversationContext();
// Model selection
const { provider, modelName } = parseModelString(config.model);
const client = getClientForProvider(provider);
const model = client(modelName);
const result = await generateText({
model,
tools: allTools,
temperature: config.temperature,
system: config.system,
prompt: contextualPrompt
});
let assistantResponse = "";
if (typeof result.text === "string" && result.text.trim() !== "") {
assistantResponse = result.text;
}
else {
assistantResponse = JSON.stringify(result, null, 2);
}
const agentMsg = {
role: "assistant",
content: assistantResponse,
timestamp: new Date()
};
setConversation((prev) => [...prev, agentMsg]);
}
catch (e) {
setError(e.message || "Unknown error");
}
finally {
setIsLoading(false);
}
}, [loadedTools, config, conversation]);
if (isLoading) {
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsxs(Text, { color: "yellow", children: [_jsx(Spinner, { type: "dots" }), " Loading configuration and tools..."] }) }));
}
if (error) {
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
}
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: "green", children: "Welcome to the Ink-based Local Agent CLI!" }), _jsxs(Text, { children: ["Type your prompt for ", _jsx(Text, { color: "yellow", children: agentName }), " (Ctrl+C to exit):"] }), _jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: conversation.map((msg, idx) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: msg.role === "user" ? "blue" : msg.role === "assistant" ? "yellow" : "gray", children: [msg.role === "user" ? "You" : agentName, ":"] }), _jsx(MarkdownRenderer, { children: msg.content })] }, idx))) }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: "Enter your prompt" })] }));
};
export default App;
//# sourceMappingURL=ink-app.js.map