local-agent
Version:
A CLI agentic system for orchestrating tools and memory with per-folder scoping
164 lines (152 loc) • 6.28 kB
text/typescript
/**
* @fileoverview
* Initialization utilities for the local agent project.
* Handles validation and creation of required configuration files, memory directory,
* and loading of Model Context Protocol (MCP) tools for agent operation.
* All log messages use the prefix [local-agent] for consistency and clarity.
*/
import { existsSync, mkdirSync, writeFileSync } from "fs";
import { DEFAULT_CONFIG, DEFAULT_TOOLS, DEFAULT_KEYS } from "./default-configs";
import { ToolsJson, KeysJson } from "./types";
import { createMcpTools } from "@agentic/mcp";
import { ConfigService } from "./config";
import { handleError, ConfigurationError } from "./errors";
/**
* Checks for missing required files and the memory directory in the project.
*
* @param {string[]} REQUIRED_FILES - List of required file names to check for existence.
* @param {string} MEMORY_DIR - Name of the memory directory to check.
* @returns {string[]} Array of missing items (file names and/or the memory directory).
*/
export function getMissingProjectFiles(REQUIRED_FILES: string[], MEMORY_DIR: string): string[] {
const missing: string[] = [];
for (const file of REQUIRED_FILES) {
if (!existsSync(file)) {
missing.push(file);
}
}
if (!existsSync(MEMORY_DIR)) {
missing.push(MEMORY_DIR + "/");
}
return missing;
}
/**
* Creates any missing required files and the memory directory for the project.
* Populates files with default content where appropriate.
*
* @param {string[]} REQUIRED_FILES - List of required file names to create if missing.
* @param {string} MEMORY_DIR - Name of the memory directory to create if missing.
*/
export function initializeProjectFiles(REQUIRED_FILES: string[], MEMORY_DIR: string) {
for (const file of REQUIRED_FILES) {
if (!existsSync(file)) {
let content = "";
if (file === "system.md") content = "";
else if (file === "mcp-tools.json") content = JSON.stringify(DEFAULT_TOOLS, null, 2);
else if (file === "local-agent.json") content = JSON.stringify(DEFAULT_CONFIG, null, 2);
else if (file === "keys.json") content = JSON.stringify(DEFAULT_KEYS, null, 2);
else if (file.endsWith(".json")) content = "{}";
writeFileSync(file, content, "utf8");
console.log(`[local-agent] Created missing file: ${file}`);
}
}
if (!existsSync(MEMORY_DIR)) {
mkdirSync(MEMORY_DIR);
console.log(`[local-agent] Created missing folder: ${MEMORY_DIR}/`);
}
}
import * as readline from "readline";
/**
* Checks for missing files/dirs, prompts user to initialize if needed, and loads config/tools/keys.
* Exits if user declines initialization or after initializing.
*/
/**
* Validates the presence of all required files and the memory directory.
* If any are missing, prompts the user to initialize the project and exits if declined.
* Loads and parses the agent configuration, MCP tools, and API keys from their respective files.
*
* @param {string[]} REQUIRED_FILES - List of required file names.
* @param {string} MEMORY_DIR - Name of the memory directory.
* @returns {Promise<{ config: any, tools: any, keys: any }>} Parsed configuration, tools, and keys.
*/
export async function validateAndLoadFiles(REQUIRED_FILES: string[], MEMORY_DIR: string) {
const missing = getMissingProjectFiles(REQUIRED_FILES, MEMORY_DIR);
if (missing.length > 0) {
console.log(`${YELLOW}[local-agent] The following required files or directories are missing:${RESET}`);
for (const item of missing) {
console.log(` - ${item}`);
}
// Wrap readline in a Promise to ensure async/await flow
await new Promise<void>((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("[local-agent] Would you like to initialize the project now? (y/n): ", (answer) => {
rl.close();
if (answer.trim().toLowerCase().startsWith("y")) {
initializeProjectFiles(REQUIRED_FILES, MEMORY_DIR);
console.log(`${GREEN}[local-agent] Project initialized. Please review the created files.${RESET}`);
process.exit(0);
} else {
console.log(`${RED}[local-agent] Project not initialized. Exiting.${RESET}`);
process.exit(1);
}
resolve();
});
});
// This line is never reached, but is required for type safety
return undefined as any;
}
let config: any;
let tools: ToolsJson;
let keys: KeysJson;
try {
config = ConfigService.loadConfig("local-agent.json");
} catch (err) {
handleError(err, 'loading configuration');
process.exit(1);
}
try {
tools = ConfigService.loadTools("mcp-tools.json");
} catch (err) {
handleError(err, 'loading tools configuration');
process.exit(1);
}
try {
keys = ConfigService.loadKeys("keys.json");
} catch (err) {
handleError(err, 'loading keys configuration');
process.exit(1);
}
return { config, tools, keys };
}
export const GREEN = "\x1b[32m";
export const RED = "\x1b[31m";
export const YELLOW = "\x1b[33m";
export const RESET = "\x1b[0m";
/**
* Loads all Model Context Protocol (MCP) tools defined in the tools configuration.
* Returns an object containing the loaded tools and a status array for each tool.
*
* @param {any} toolsConfig - The parsed tools configuration object (from mcp-tools.json).
* @returns {Promise<{ loadedTools: Record<string, any>, toolStatus: Array<{name: string, status: "success"|"fail", error?: string}> }>}
* An object with loaded MCP tools and their load status.
*/
export async function loadAllMcpTools(toolsConfig: any) {
const loadedTools: Record<string, any> = {};
const toolStatus: Array<{ name: string; status: "success" | "fail"; error?: string }> = [];
for (const [name, serverProcess] of Object.entries(toolsConfig.mcpServers || {})) {
try {
const tool = await createMcpTools({
name: `agentic-mcp-${name}`,
serverProcess: serverProcess as any
});
loadedTools[name] = tool;
toolStatus.push({ name, status: "success" });
} catch (err) {
toolStatus.push({ name, status: "fail", error: err instanceof Error ? err.message : String(err) });
}
}
return { loadedTools, toolStatus };
}