@react-frameui/loki-ai 1.0.0

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,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listFiles = listFiles;
7
+ exports.readFile = readFile;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ /**
11
+ * File system tool for read-only access.
12
+ */
13
+ // Safety blocks
14
+ const IGNORED_DIRS = ['node_modules', '.git', 'dist', 'out', '.idea', '.vscode'];
15
+ const BINARY_EXTS = ['.png', '.jpg', '.jpeg', '.exe', '.bin', '.dll', '.so', '.dylib', '.zip', '.tar', '.gz'];
16
+ // Helper to determine safety
17
+ function isSafePath(targetPath) {
18
+ // Resolve absolute path
19
+ const resolved = path_1.default.resolve(targetPath);
20
+ // Basic check: must ensure we don't go outside legitimate boundaries?
21
+ // For a local assistant, user has full access, but we should block sensitive dirs if possible.
22
+ // For now, we trust the local user but block binary/junk folders.
23
+ return true;
24
+ }
25
+ function listFiles(targetPath = '.') {
26
+ try {
27
+ if (!fs_1.default.existsSync(targetPath))
28
+ return ["Path does not exist."];
29
+ const stats = fs_1.default.statSync(targetPath);
30
+ if (!stats.isDirectory())
31
+ return ["Path is not a directory."];
32
+ const entries = fs_1.default.readdirSync(targetPath);
33
+ // Filter ignored
34
+ const filtered = entries.filter(e => !IGNORED_DIRS.includes(e));
35
+ // Sort: directories first, then files
36
+ const dirs = filtered.filter(e => fs_1.default.statSync(path_1.default.join(targetPath, e)).isDirectory());
37
+ const files = filtered.filter(e => !fs_1.default.statSync(path_1.default.join(targetPath, e)).isDirectory());
38
+ return [...dirs.map(d => `${d}/`), ...files];
39
+ }
40
+ catch (e) {
41
+ return [`Error listing files: ${e.message}`];
42
+ }
43
+ }
44
+ function readFile(targetPath) {
45
+ try {
46
+ if (!fs_1.default.existsSync(targetPath))
47
+ return "File does not exist.";
48
+ const stats = fs_1.default.statSync(targetPath);
49
+ if (stats.isDirectory())
50
+ return "Target is a directory, not a file.";
51
+ const ext = path_1.default.extname(targetPath).toLowerCase();
52
+ if (BINARY_EXTS.includes(ext))
53
+ return "Binary file detected. Cannot read text content.";
54
+ return fs_1.default.readFileSync(targetPath, 'utf-8');
55
+ }
56
+ catch (e) {
57
+ return `Error reading file: ${e.message}`;
58
+ }
59
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isGitRepo = isGitRepo;
4
+ exports.getGitStatus = getGitStatus;
5
+ exports.getRecentLog = getRecentLog;
6
+ exports.getDiff = getDiff;
7
+ exports.getBranches = getBranches;
8
+ const child_process_1 = require("child_process");
9
+ function isGitRepo(cwd = process.cwd()) {
10
+ try {
11
+ (0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', { cwd, stdio: 'ignore' });
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ function getGitStatus(cwd = process.cwd()) {
19
+ try {
20
+ return (0, child_process_1.execSync)('git status --short', { cwd, encoding: 'utf-8' }).trim();
21
+ }
22
+ catch {
23
+ return 'Error getting git status';
24
+ }
25
+ }
26
+ function getRecentLog(cwd = process.cwd(), count = 5) {
27
+ try {
28
+ // oneline, graph, decorate
29
+ return (0, child_process_1.execSync)(`git log -n ${count} --pretty=format:"%h - %an, %ar : %s"`, { cwd, encoding: 'utf-8' }).trim();
30
+ }
31
+ catch {
32
+ return 'Error getting git log';
33
+ }
34
+ }
35
+ function getDiff(cwd = process.cwd()) {
36
+ try {
37
+ // Limit diff size to prevent token explosion
38
+ const diff = (0, child_process_1.execSync)('git diff HEAD', { cwd, encoding: 'utf-8' });
39
+ if (diff.length > 2000)
40
+ return diff.substring(0, 2000) + '\n...[Diff Truncated]';
41
+ return diff || 'No changes.';
42
+ }
43
+ catch {
44
+ return 'Error getting diff';
45
+ }
46
+ }
47
+ function getBranches(cwd = process.cwd()) {
48
+ try {
49
+ return (0, child_process_1.execSync)('git branch', { cwd, encoding: 'utf-8' }).trim();
50
+ }
51
+ catch {
52
+ return 'Error getting branches';
53
+ }
54
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Safe Math routing to evaluate simple expressions.
4
+ * Avoids eval().
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.isMathExpression = isMathExpression;
8
+ exports.evaluateMath = evaluateMath;
9
+ // Supported operators
10
+ const OPERATORS = ['+', '-', '*', '/', '%', '(', ')'];
11
+ function isMathExpression(input) {
12
+ // Basic heuristic: check if input contains digits and operators
13
+ // and DOES NOT contain letters (except for specific math keywords like 'sqrt' or 'pi' handled later)
14
+ // For specific keywords like sqrt, we need a robust check.
15
+ const trimmed = input.replace(/\s+/g, '').toLowerCase();
16
+ // Must have at least one digit
17
+ if (!/\d/.test(trimmed))
18
+ return false;
19
+ // Check against allowed chars: digits, ., operators, parenthese
20
+ // also "sqrt", "pow", "abs" etc?
21
+ // Let's stick to simple arithmetic first + math functions if strict matching allows.
22
+ // Simplification: valid matching for our lightweight eval
23
+ // 1. Digits 0-9
24
+ // 2. Operators + - * / % ^
25
+ // 3. Parentheses ( )
26
+ // 4. "sqrt", "sin", "cos", "tan", "log", "pi"
27
+ const validPattern = /^[\d\.\+\-\*\/\%\(\)\s\^]|(sqrt|sin|cos|tan|log|pi|e)+$/;
28
+ // If ANY character is NOT in our allowlist, it's not math.
29
+ // We check each char (or word structure).
30
+ // Actually, simpler approach:
31
+ // Remove known functions and constants
32
+ let checkStr = trimmed
33
+ .replace(/sqrt|sin|cos|tan|log|pi|e/g, '')
34
+ .replace(/[\d\.\+\-\*\/\%\(\)\^]/g, '');
35
+ return checkStr.length === 0;
36
+ }
37
+ function evaluateMath(expression) {
38
+ try {
39
+ // Sanitize
40
+ // we use Function constructor as a safer eval replacement FOR MATH ONLY
41
+ // after strict validation.
42
+ // Replace 'sqrt' with 'Math.sqrt', etc.
43
+ let sanitized = expression.toLowerCase()
44
+ .replace(/\^/g, '**') // Power operator
45
+ .replace(/sqrt/g, 'Math.sqrt')
46
+ .replace(/sin/g, 'Math.sin')
47
+ .replace(/cos/g, 'Math.cos')
48
+ .replace(/tan/g, 'Math.tan')
49
+ .replace(/log/g, 'Math.log')
50
+ .replace(/\bpi\b/g, 'Math.PI')
51
+ .replace(/\be\b/g, 'Math.E');
52
+ // Validation again to ensure NO malicious code slipped in
53
+ // e.g. "Math.sqrt(alert(1))" -> contains letters not in Math.*
54
+ // Our isMathExpression check should have caught generic letters.
55
+ // But let's be super safe:
56
+ // Attempt execution in a sandbox-like scope (Function)
57
+ // Note: Function(string) is still eval-like but restricted to global scope interaction.
58
+ // Given we pre-validated, it reduces risk significantly.
59
+ const f = new Function(`return (${sanitized})`);
60
+ const result = f();
61
+ if (typeof result === 'number' && !isNaN(result) && isFinite(result)) {
62
+ // Round to reasonable decimals if float
63
+ return parseFloat(result.toFixed(4)).toString();
64
+ }
65
+ return "Invalid math result";
66
+ }
67
+ catch (e) {
68
+ return "Error evaluating expression";
69
+ }
70
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /**
3
+ * Handles time and date queries with timezone support.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getCurrentTime = getCurrentTime;
7
+ exports.getCurrentDate = getCurrentDate;
8
+ // Mapping of common timezone aliases to IANA identifiers
9
+ const TIMEZONE_MAP = {
10
+ 'utc': 'UTC',
11
+ 'gmt': 'GMT',
12
+ 'ist': 'Asia/Kolkata',
13
+ 'pst': 'America/Los_Angeles',
14
+ 'est': 'America/New_York',
15
+ 'pdt': 'America/Los_Angeles', // Daylight
16
+ 'edt': 'America/New_York', // Daylight
17
+ 'cet': 'Europe/Paris',
18
+ 'bst': 'Europe/London',
19
+ 'ny': 'America/New_York',
20
+ 'india': 'Asia/Kolkata',
21
+ 'london': 'Europe/London'
22
+ };
23
+ function resolveTimezone(tz) {
24
+ if (!tz)
25
+ return undefined;
26
+ const normalized = tz.toLowerCase().trim();
27
+ if (TIMEZONE_MAP[normalized]) {
28
+ return TIMEZONE_MAP[normalized];
29
+ }
30
+ // Attempt use directly if valid IANA (e.g. Asia/Tokyo)
31
+ try {
32
+ Intl.DateTimeFormat(undefined, { timeZone: tz });
33
+ return tz;
34
+ }
35
+ catch (e) {
36
+ return undefined; // Fallback to system
37
+ }
38
+ }
39
+ function getCurrentTime(timezone) {
40
+ const tz = resolveTimezone(timezone);
41
+ const now = new Date();
42
+ try {
43
+ const timeString = now.toLocaleTimeString('en-US', {
44
+ hour: '2-digit',
45
+ minute: '2-digit',
46
+ timeZone: tz,
47
+ timeZoneName: 'short'
48
+ });
49
+ return timeString;
50
+ }
51
+ catch {
52
+ return "Invalid timezone specified.";
53
+ }
54
+ }
55
+ function getCurrentDate(timezone) {
56
+ const tz = resolveTimezone(timezone);
57
+ const now = new Date();
58
+ try {
59
+ const dateString = now.toLocaleDateString('en-US', {
60
+ weekday: 'long',
61
+ year: 'numeric',
62
+ month: 'long',
63
+ day: 'numeric',
64
+ timeZone: tz
65
+ });
66
+ return dateString;
67
+ }
68
+ catch {
69
+ return "Invalid timezone specified.";
70
+ }
71
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOOLS = void 0;
4
+ exports.getToolSchemas = getToolSchemas;
5
+ exports.executeToolCall = executeToolCall;
6
+ const timeTool_1 = require("./timeTool");
7
+ const mathTool_1 = require("./mathTool");
8
+ const fsTool_1 = require("./fsTool");
9
+ const gitTool_1 = require("./gitTool");
10
+ const projectContext_1 = require("../context/projectContext");
11
+ exports.TOOLS = {
12
+ get_time: {
13
+ name: 'get_time',
14
+ description: 'Get current time. Args: timezone (optional)',
15
+ parameters: '{ timezone?: string }',
16
+ execute: (args) => (0, timeTool_1.getCurrentTime)(args?.timezone)
17
+ },
18
+ get_date: {
19
+ name: 'get_date',
20
+ description: 'Get current date. Args: timezone (optional)',
21
+ parameters: '{ timezone?: string }',
22
+ execute: (args) => (0, timeTool_1.getCurrentDate)(args?.timezone)
23
+ },
24
+ calculate: {
25
+ name: 'calculate',
26
+ description: 'Evaluate math expression.',
27
+ parameters: '{ expression: string }',
28
+ execute: (args) => (0, mathTool_1.evaluateMath)(args.expression)
29
+ },
30
+ list_files: {
31
+ name: 'list_files',
32
+ description: 'List files in a directory.',
33
+ parameters: '{ path?: string }',
34
+ execute: (args) => (0, fsTool_1.listFiles)(args?.path || '.').join(', ')
35
+ },
36
+ read_file: {
37
+ name: 'read_file',
38
+ description: 'Read file content.',
39
+ parameters: '{ path: string }',
40
+ execute: (args) => (0, fsTool_1.readFile)(args.path)
41
+ },
42
+ git_status: {
43
+ name: 'git_status',
44
+ description: 'Get git status summary.',
45
+ parameters: '{}',
46
+ execute: () => (0, gitTool_1.getGitStatus)()
47
+ },
48
+ git_log: {
49
+ name: 'git_log',
50
+ description: 'Get recent git commits.',
51
+ parameters: '{ count?: number }',
52
+ execute: (args) => (0, gitTool_1.getRecentLog)(undefined, args?.count)
53
+ },
54
+ git_diff: {
55
+ name: 'git_diff',
56
+ description: 'Get git diff.',
57
+ parameters: '{}',
58
+ execute: () => (0, gitTool_1.getDiff)()
59
+ },
60
+ project_context: {
61
+ name: 'project_context',
62
+ description: 'Get full project overview.',
63
+ parameters: '{}',
64
+ execute: () => (0, projectContext_1.formatProjectContext)((0, projectContext_1.getProjectContext)())
65
+ }
66
+ };
67
+ function getToolSchemas() {
68
+ return Object.values(exports.TOOLS).map(t => `- Tool: ${t.name}\n Desc: ${t.description}\n Params: ${t.parameters}`).join('\n\n');
69
+ }
70
+ async function executeToolCall(toolName, args) {
71
+ const tool = exports.TOOLS[toolName];
72
+ if (!tool)
73
+ return `Error: Tool ${toolName} not found.`;
74
+ try {
75
+ return await tool.execute(args);
76
+ }
77
+ catch (e) {
78
+ return `Error executing ${toolName}: ${e.message}`;
79
+ }
80
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CONFIG = void 0;
7
+ const dotenv_1 = __importDefault(require("dotenv"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ // Load environment variables
11
+ dotenv_1.default.config();
12
+ const HOME_DIR = os_1.default.homedir();
13
+ const CONFIG_DIR = path_1.default.join(HOME_DIR, '.loki');
14
+ exports.CONFIG = {
15
+ // App Info
16
+ APP_NAME: 'Loki',
17
+ VERSION: '1.2.0',
18
+ CONFIG_DIR,
19
+ MEMORY_FILE: path_1.default.join(CONFIG_DIR, 'memory.json'),
20
+ SEMANTIC_MEMORY_DIR: path_1.default.join(CONFIG_DIR, 'semantic-memory'),
21
+ SEMANTIC_INDEX_FILE: path_1.default.join(CONFIG_DIR, 'semantic-memory', 'index.json'),
22
+ // LLM Defaults
23
+ DEFAULT_PROVIDER: 'ollama',
24
+ // Ollama
25
+ OLLAMA_HOST: process.env.OLLAMA_HOST || 'http://localhost:11434',
26
+ OLLAMA_MODEL: process.env.OLLAMA_MODEL || 'mistral:7b',
27
+ OLLAMA_EMBED_MODEL: process.env.OLLAMA_EMBED_MODEL || 'nomic-embed-text',
28
+ // Groq
29
+ GROQ_API_KEY: process.env.GROQ_API_KEY,
30
+ GROQ_MODEL: 'llama-3.1-8b-instant',
31
+ GROQ_API_URL: 'https://api.groq.com/openai/v1/chat/completions',
32
+ // Limits
33
+ MAX_MEMORY_ITEMS: 20,
34
+ MAX_SEMANTIC_RESULTS: 5,
35
+ MAX_CONTEXT_CHARS: 6000,
36
+ TIMEOUT: 60000,
37
+ // Feature Flags (Optional Engines)
38
+ USE_LANGCHAIN: process.env.USE_LANGCHAIN === 'true',
39
+ USE_LANGGRAPH: process.env.USE_LANGGRAPH === 'true',
40
+ // WhatsApp
41
+ WHATSAPP_TOKEN: process.env.WHATSAPP_TOKEN,
42
+ WHATSAPP_PHONE_NUMBER_ID: process.env.WHATSAPP_PHONE_NUMBER_ID,
43
+ WHATSAPP_VERIFY_TOKEN: process.env.WHATSAPP_VERIFY_TOKEN || 'loki_secure_verify',
44
+ WHATSAPP_PORT: parseInt(process.env.WHATSAPP_PORT || '3000'),
45
+ };
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Spinner = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ class Spinner {
9
+ constructor(text = '') {
10
+ this.text = text;
11
+ this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
12
+ this.interval = null;
13
+ this.current = 0;
14
+ this.isSpinning = false;
15
+ }
16
+ start() {
17
+ if (this.isSpinning)
18
+ return;
19
+ this.isSpinning = true;
20
+ process.stdout.write('\x1B[?25l'); // Hide cursor
21
+ this.interval = setInterval(() => {
22
+ // Clear line from cursor position to end (simplified: just backspace and rewrite)
23
+ // We assume we are appending to the current line.
24
+ // To properly "animate" without destroying previous text on the line (like the prefix),
25
+ // we essentially print the frame, then backspace.
26
+ const frame = chalk_1.default.gray(this.frames[this.current]);
27
+ process.stdout.write(frame);
28
+ process.stdout.write(`\b`); // Move back 1 char (assuming frame is 1 char)
29
+ // Note: Braille chars are usually 1 column in terminals.
30
+ this.current = (this.current + 1) % this.frames.length;
31
+ }, 80);
32
+ }
33
+ stop() {
34
+ if (!this.isSpinning)
35
+ return;
36
+ if (this.interval)
37
+ clearInterval(this.interval);
38
+ this.isSpinning = false;
39
+ // Clear the spinner character
40
+ process.stdout.write(' '); // overwrite with space
41
+ process.stdout.write('\b'); // back to position
42
+ process.stdout.write('\x1B[?25h'); // Show cursor
43
+ }
44
+ }
45
+ exports.Spinner = Spinner;
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@react-frameui/loki-ai",
3
+ "version": "1.0.0",
4
+ "description": "LOKI - Local Omni Knowledge Interface. A powerful CLI AI assistant with tools, memory, WhatsApp integration, and more.",
5
+ "keywords": [
6
+ "ai",
7
+ "assistant",
8
+ "cli",
9
+ "chatbot",
10
+ "ollama",
11
+ "langchain",
12
+ "whatsapp",
13
+ "local-ai",
14
+ "tui",
15
+ "terminal"
16
+ ],
17
+ "homepage": "https://github.com/IamNishant51/LOKI#readme",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/IamNishant51/LOKI.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/IamNishant51/LOKI/issues"
24
+ },
25
+ "bin": {
26
+ "loki": "./dist/index.js"
27
+ },
28
+ "main": "dist/index.js",
29
+ "types": "dist/index.d.ts",
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "dev": "ts-node src/index.ts",
37
+ "build": "tsc",
38
+ "start": "node dist/index.js",
39
+ "prepublishOnly": "npm run build",
40
+ "test": "echo \"No tests yet\" && exit 0"
41
+ },
42
+ "author": "Nishant <iamnishant51@github.com>",
43
+ "license": "MIT",
44
+ "dependencies": {
45
+ "@langchain/community": "^1.1.9",
46
+ "@langchain/core": "^1.1.17",
47
+ "@langchain/langgraph": "^1.1.2",
48
+ "@types/node": "^25.1.0",
49
+ "@whiskeysockets/baileys": "^7.0.0-rc.9",
50
+ "axios": "^1.13.4",
51
+ "body-parser": "^2.2.2",
52
+ "chalk": "^4.1.2",
53
+ "commander": "^14.0.2",
54
+ "cors": "^2.8.6",
55
+ "dotenv": "^17.2.3",
56
+ "express": "^5.2.1",
57
+ "langchain": "^1.2.15",
58
+ "pino": "^10.3.0",
59
+ "qrcode": "^1.5.4",
60
+ "qrcode-terminal": "^0.12.0",
61
+ "ts-node": "^10.9.2",
62
+ "typescript": "^5.9.3",
63
+ "whatsapp-web.js": "^1.34.4",
64
+ "zod": "^4.3.6"
65
+ },
66
+ "devDependencies": {
67
+ "@types/body-parser": "^1.19.6",
68
+ "@types/express": "^5.0.6",
69
+ "@types/qrcode-terminal": "^0.12.2"
70
+ },
71
+ "engines": {
72
+ "node": ">=18.0.0"
73
+ }
74
+ }