local-agent
Version:
A CLI agentic system for orchestrating tools and memory with per-folder scoping
270 lines • 12.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
// Mock dependencies BEFORE importing modules
jest.mock('fs');
jest.mock('readline');
jest.mock('@agentic/mcp', () => ({
createMcpTools: jest.fn()
}));
const fs_1 = require("fs");
const readline = __importStar(require("readline"));
const initialization_1 = require("../src/initialization");
const default_configs_1 = require("../src/default-configs");
const mockExistsSync = fs_1.existsSync;
const mockMkdirSync = fs_1.mkdirSync;
const mockWriteFileSync = fs_1.writeFileSync;
const mockReadFileSync = fs_1.readFileSync;
const mockReadline = readline;
describe('initialization.ts', () => {
const REQUIRED_FILES = ['system.md', 'local-agent.json', 'mcp-tools.json', 'keys.json'];
const MEMORY_DIR = 'memory';
beforeEach(() => {
jest.clearAllMocks();
});
describe('getMissingProjectFiles', () => {
it('should return empty array when all files exist', () => {
mockExistsSync.mockReturnValue(true);
const result = (0, initialization_1.getMissingProjectFiles)(REQUIRED_FILES, MEMORY_DIR);
expect(result).toEqual([]);
expect(mockExistsSync).toHaveBeenCalledTimes(5); // 4 files + 1 directory
});
it('should return missing files', () => {
mockExistsSync.mockImplementation((path) => {
return path !== 'system.md' && path !== 'memory';
});
const result = (0, initialization_1.getMissingProjectFiles)(REQUIRED_FILES, MEMORY_DIR);
expect(result).toEqual(['system.md', 'memory/']);
});
it('should return all files when none exist', () => {
mockExistsSync.mockReturnValue(false);
const result = (0, initialization_1.getMissingProjectFiles)(REQUIRED_FILES, MEMORY_DIR);
expect(result).toEqual([
'system.md',
'local-agent.json',
'mcp-tools.json',
'keys.json',
'memory/'
]);
});
});
describe('initializeProjectFiles', () => {
beforeEach(() => {
mockExistsSync.mockReturnValue(false);
});
it('should create all missing files with correct content', () => {
(0, initialization_1.initializeProjectFiles)(REQUIRED_FILES, MEMORY_DIR);
expect(mockWriteFileSync).toHaveBeenCalledWith('system.md', '', 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledWith('local-agent.json', JSON.stringify(default_configs_1.DEFAULT_CONFIG, null, 2), 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledWith('mcp-tools.json', JSON.stringify(default_configs_1.DEFAULT_TOOLS, null, 2), 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledWith('keys.json', JSON.stringify(default_configs_1.DEFAULT_KEYS, null, 2), 'utf8');
expect(mockMkdirSync).toHaveBeenCalledWith(MEMORY_DIR);
});
it('should not create files that already exist', () => {
mockExistsSync.mockImplementation((path) => path === 'system.md');
(0, initialization_1.initializeProjectFiles)(REQUIRED_FILES, MEMORY_DIR);
expect(mockWriteFileSync).toHaveBeenCalledTimes(3); // Only 3 files created
expect(mockWriteFileSync).not.toHaveBeenCalledWith('system.md', expect.any(String), 'utf8');
});
it('should create directory if it does not exist', () => {
mockExistsSync.mockImplementation((path) => path !== MEMORY_DIR);
(0, initialization_1.initializeProjectFiles)(REQUIRED_FILES, MEMORY_DIR);
expect(mockMkdirSync).toHaveBeenCalledWith(MEMORY_DIR);
});
it('should handle generic JSON files', () => {
const customFiles = ['custom.json'];
mockExistsSync.mockReturnValue(false);
(0, initialization_1.initializeProjectFiles)(customFiles, MEMORY_DIR);
expect(mockWriteFileSync).toHaveBeenCalledWith('custom.json', '{}', 'utf8');
});
});
describe('validateAndLoadFiles', () => {
it('should load and parse files when all exist', async () => {
mockExistsSync.mockReturnValue(true);
mockReadFileSync.mockImplementation((path) => {
if (path === 'local-agent.json') {
return JSON.stringify(default_configs_1.DEFAULT_CONFIG);
}
if (path === 'mcp-tools.json') {
return JSON.stringify(default_configs_1.DEFAULT_TOOLS);
}
if (path === 'keys.json') {
return JSON.stringify(default_configs_1.DEFAULT_KEYS);
}
return '{}';
});
const result = await (0, initialization_1.validateAndLoadFiles)(REQUIRED_FILES, MEMORY_DIR);
expect(result.config).toBeDefined();
expect(result.tools).toBeDefined();
expect(result.keys).toBeDefined();
});
it('should prompt user when files are missing and user accepts', async () => {
mockExistsSync.mockReturnValue(false);
const mockRl = {
question: jest.fn((question, callback) => {
callback('y');
}),
close: jest.fn()
};
mockReadline.createInterface.mockReturnValue(mockRl);
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
await expect((0, initialization_1.validateAndLoadFiles)(REQUIRED_FILES, MEMORY_DIR)).rejects.toThrow('process.exit called');
expect(mockExit).toHaveBeenCalledWith(0);
mockExit.mockRestore();
});
it('should exit when user declines initialization', async () => {
mockExistsSync.mockReturnValue(false);
const mockRl = {
question: jest.fn((question, callback) => {
callback('n');
}),
close: jest.fn()
};
mockReadline.createInterface.mockReturnValue(mockRl);
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
await expect((0, initialization_1.validateAndLoadFiles)(REQUIRED_FILES, MEMORY_DIR)).rejects.toThrow('process.exit called');
expect(mockExit).toHaveBeenCalledWith(1);
mockExit.mockRestore();
});
it('should handle invalid JSON in config files', async () => {
mockExistsSync.mockReturnValue(true);
mockReadFileSync.mockImplementation((path) => {
if (path === 'local-agent.json') {
return 'invalid json';
}
return JSON.stringify({});
});
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
await expect((0, initialization_1.validateAndLoadFiles)(REQUIRED_FILES, MEMORY_DIR)).rejects.toThrow('process.exit called');
expect(mockExit).toHaveBeenCalledWith(1);
mockExit.mockRestore();
});
});
describe('loadAllMcpTools', () => {
const { createMcpTools } = require('@agentic/mcp');
const mockCreateMcpTools = createMcpTools;
it('should load all MCP tools successfully', async () => {
const toolsConfig = {
mcpServers: {
filesystem: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp']
},
memory: {
command: 'npx',
args: ['-y', '@aakarsh-sasi/memory-bank-mcp']
}
}
};
const mockTool1 = { name: 'filesystem-tool' };
const mockTool2 = { name: 'memory-tool' };
mockCreateMcpTools
.mockResolvedValueOnce(mockTool1)
.mockResolvedValueOnce(mockTool2);
const result = await (0, initialization_1.loadAllMcpTools)(toolsConfig);
expect(result.loadedTools).toEqual({
filesystem: mockTool1,
memory: mockTool2
});
expect(result.toolStatus).toEqual([
{ name: 'filesystem', status: 'success' },
{ name: 'memory', status: 'success' }
]);
});
it('should handle tool loading failures', async () => {
const toolsConfig = {
mcpServers: {
filesystem: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp']
},
failing: {
command: 'invalid-command',
args: []
}
}
};
const mockTool = { name: 'filesystem-tool' };
const error = new Error('Tool loading failed');
mockCreateMcpTools
.mockResolvedValueOnce(mockTool)
.mockRejectedValueOnce(error);
const result = await (0, initialization_1.loadAllMcpTools)(toolsConfig);
expect(result.loadedTools).toEqual({
filesystem: mockTool
});
expect(result.toolStatus).toEqual([
{ name: 'filesystem', status: 'success' },
{ name: 'failing', status: 'fail', error: 'Tool loading failed' }
]);
});
it('should handle empty tools config', async () => {
const toolsConfig = { mcpServers: {} };
const result = await (0, initialization_1.loadAllMcpTools)(toolsConfig);
expect(result.loadedTools).toEqual({});
expect(result.toolStatus).toEqual([]);
});
it('should handle non-Error exceptions', async () => {
const toolsConfig = {
mcpServers: {
failing: {
command: 'invalid-command',
args: []
}
}
};
mockCreateMcpTools.mockRejectedValueOnce('String error');
const result = await (0, initialization_1.loadAllMcpTools)(toolsConfig);
expect(result.toolStatus).toEqual([
{ name: 'failing', status: 'fail', error: 'String error' }
]);
});
});
describe('Color constants', () => {
it('should export correct ANSI color codes', () => {
expect(initialization_1.GREEN).toBe('\x1b[32m');
expect(initialization_1.RED).toBe('\x1b[31m');
expect(initialization_1.YELLOW).toBe('\x1b[33m');
expect(initialization_1.RESET).toBe('\x1b[0m');
});
});
});
//# sourceMappingURL=initialization.test.js.map