src / toolsProvider.ts
// src/toolsProvider.ts
import { text, tool, type Tool, type ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { spawn } from "child_process";
import {
writeFile,
readFile,
appendFile,
access,
mkdir,
rm,
rename,
cp,
readdir,
stat,
} from "fs/promises";
import { join, dirname, extname, normalize } from "path";
import { existsSync, writeFileSync, mkdirSync } from "fs";
import { findLMStudioHome } from "./findLMStudioHome.js";
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 1. الثوابت والمسارات
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const LMSTUDIO_HOME = findLMStudioHome();
const targetBase = process.env.DEEPEDITOR_BASE || normalize("D:\\bido");
function resolveInsideBase(rel: string): string {
const isAbs = /^([A-Za-z]:)?[\\\/]/.test(rel);
return normalize(isAbs ? rel : join(targetBase, rel));
}
async function ensureDirectory(dirPath: string) {
if (!existsSync(dirPath)) await mkdir(dirPath, { recursive: true });
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 2. دوال الأدوات المستقلة
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ── أداة البحث على الإنترنت (مستقلة) ──
async function searchWebTool(params: {
query: string;
engine?: "duckduckgo" | "google";
maxResults?: number;
}) {
try {
// استيراد serpapi ديناميكياً (لتفادي مشاكل التحميل)
const { getJson } = await import('serpapi');
const API_KEY = 'cd86d518973b79f8e6600672666bff94c590e4b0d2f50be247a2659c03ec1b62';
console.log('🔍 جاري البحث عن: "' + params.query + '" عبر SerpAPI...');
const response = await getJson({
engine: 'google_news',
api_key: API_KEY,
q: params.query,
hl: 'ar',
gl: 'eg',
num: params.maxResults || 5
});
let results: any[] = [];
if (response.news_results && response.news_results.length > 0) {
for (let i = 0; i < response.news_results.length && i < (params.maxResults || 5); i++) {
const item = response.news_results[i];
if (!item.link || item.link === '#') continue;
let source = 'Google News';
if (item.source) {
if (typeof item.source === 'string') source = item.source;
else if (item.source.name) source = item.source.name;
}
let snippet = item.snippet || item.description || item.summary || '';
if (typeof snippet !== 'string') snippet = JSON.stringify(snippet);
if (!snippet || snippet === 'لا يوجد وصف' || snippet.trim() === '') {
snippet = item.title || 'لا يوجد وصف';
}
let published = item.date || new Date().toISOString();
if (typeof published !== 'string') published = JSON.stringify(published);
let title = item.title || 'بدون عنوان';
title = title.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
results.push({ title, url: item.link, snippet, source, published });
}
}
// إذا لم تكن هناك نتائج عربية، جرب الإنجليزية
if (results.length === 0) {
console.log('⚠️ لا توجد نتائج عربية، جاري البحث بالإنجليزية...');
const cleanQuery = params.query.replace(/[^\w\s]/g, '').trim();
const englishResponse = await getJson({
engine: 'google_news',
api_key: API_KEY,
q: cleanQuery || 'artificial intelligence',
hl: 'en',
gl: 'us',
num: params.maxResults || 5
});
if (englishResponse.news_results) {
for (let j = 0; j < englishResponse.news_results.length && j < (params.maxResults || 5); j++) {
const item = englishResponse.news_results[j];
if (!item.link || item.link === '#') continue;
let source = 'Google News';
if (item.source) {
if (typeof item.source === 'string') source = item.source;
else if (item.source.name) source = item.source.name;
}
let snippet = item.snippet || item.description || item.summary || '';
if (typeof snippet !== 'string') snippet = JSON.stringify(snippet);
if (!snippet || snippet === 'لا يوجد وصف' || snippet.trim() === '') {
snippet = item.title || 'لا يوجد وصف';
}
let published = item.date || new Date().toISOString();
if (typeof published !== 'string') published = JSON.stringify(published);
let title = item.title || 'بدون عنوان';
title = title.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
results.push({ title, url: item.link, snippet, source, published });
}
}
}
if (results.length === 0) {
return {
success: false,
error: 'لم يتم العثور على نتائج',
system_note: '🌐 تم البحث عن "' + params.query + '" في google'
};
}
let displayLines = ['🌐 نتائج البحث عن: ' + params.query];
for (let i = 0; i < results.length; i++) {
const r = results[i];
displayLines.push(
'\n' + (i+1) + '. ' + r.title,
' 📰 ' + r.source + ' | 📅 ' + r.published,
' 📄 ' + r.snippet,
' 🔗 ' + r.url
);
}
return {
success: true,
results: results,
display: displayLines.join('\n'),
total: results.length,
system_note: '🌐 تم البحث عن "' + params.query + '" في google'
};
} catch (error: any) {
console.error('❌ خطأ في البحث:', error.message);
return {
success: false,
error: 'فشل البحث: ' + error.message,
system_note: '⚠️ فشل البحث: ' + error.message
};
}
}
// ── أداة تحميل المقالات (مستقلة) ──
async function fetchArticleTool(params: {
url: string;
filename?: string;
}) {
try {
// استيراد node-fetch ديناميكياً
const fetch = (await import('node-fetch')).default;
console.log('📥 جاري تحميل المقال من: ' + params.url);
const response = await fetch(params.url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const html = await response.text();
// استخراج العنوان
let title = 'بدون عنوان';
const titleMatch = html.match(/<title>(.*?)<\/title>/);
if (titleMatch) {
title = titleMatch[1].replace(/<[^>]*>/g, '').trim();
}
// تنظيف النص
let textContent = html
.replace(/<script[\s\S]*?<\/script>/g, ' ')
.replace(/<style[\s\S]*?<\/style>/g, ' ')
.replace(/<[^>]*>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
// اختصار النص
const maxChars = 5000;
let content = textContent;
if (content.length > maxChars) {
content = content.substring(0, maxChars) + '\n\n... (المقال مختصر)';
}
// إنشاء اسم الملف
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const safeTitle = title.replace(/[^a-zA-Z0-9\u0600-\u06FF]/g, '_').slice(0, 50);
const fileName = params.filename || safeTitle + '_' + timestamp;
const saveDir = join(targetBase, 'internet_search_results');
if (!existsSync(saveDir)) {
mkdirSync(saveDir, { recursive: true });
}
const filePath = join(saveDir, fileName + '.md');
const markdownContent = '# ' + title + '\n\n' +
'**المصدر:** ' + params.url + '\n' +
'**تاريخ التحميل:** ' + new Date().toLocaleString('ar-EG') + '\n\n' +
'---\n\n' +
content;
writeFileSync(filePath, markdownContent, 'utf-8');
console.log('✅ تم حفظ المقال في: ' + filePath);
return {
success: true,
message: '✅ تم حفظ المقال في: ' + filePath,
file: filePath,
title: title,
wordCount: textContent.split(/\s+/).length,
charCount: textContent.length
};
} catch (error: any) {
console.error('❌ خطأ في تحميل المقال:', error.message);
return {
success: false,
error: 'فشل تحميل المقال: ' + error.message
};
}
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 3. التسجيل النهائي في LM Studio
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
export async function toolsProvider(ctl: ToolsProviderController): Promise<Tool[]> {
const tools: Tool[] = [];
// ── 1. كتابة ملف متقدم ──
tools.push(
tool({
name: "writeFileadvanced",
description: text`اكتب محتوى إلى ملف (ينشئ المجلدات تلقائياً)`,
parameters: {
file: z.string().min(1).describe("مسار الملف"),
content: z.string().describe("المحتوى المراد كتابته"),
},
implementation: async ({ file, content }) => {
try {
const path = resolveInsideBase(file);
await ensureDirectory(dirname(path));
await writeFile(path, content, "utf-8");
return { success: true, message: `تم كتابة الملف ${file}` };
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 2. قراءة ملف متقدم ──
tools.push(
tool({
name: "readFileadvanced",
description: text`اقرأ محتوى ملف نصي`,
parameters: {
file: z.string().min(1).describe("مسار الملف"),
},
implementation: async ({ file }) => {
try {
const path = resolveInsideBase(file);
if (!existsSync(path)) {
return { success: false, error: `الملف غير موجود: ${file}` };
}
const content = await readFile(path, "utf-8");
return { success: true, content, size: content.length };
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 3. الإضافة إلى ملف ──
tools.push(
tool({
name: "appendToFile",
description: text`📝 أضف محتوى إلى نهاية ملف (ينشئ الملف إذا لم يكن موجوداً)`,
parameters: {
filePath: z.string().min(1).describe("مسار الملف"),
content: z.string().min(1).describe("المحتوى المراد إضافته"),
createIfNotExists: z.boolean().default(true).describe("إنشاء الملف إذا لم يكن موجوداً"),
addNewLine: z.boolean().default(true).describe("إضافة سطر جديد قبل المحتوى"),
},
implementation: async ({ filePath, content, createIfNotExists, addNewLine }) => {
try {
const fullPath = resolveInsideBase(filePath);
const exists = await access(fullPath).then(() => true).catch(() => false);
if (!exists && !createIfNotExists) {
return {
success: false,
error: `الملف غير موجود: ${filePath}`,
hint: "استخدم createIfNotExists=true لإنشائه"
};
}
let contentToAppend = content;
if (addNewLine !== false) {
contentToAppend = "\n" + contentToAppend;
}
await ensureDirectory(dirname(fullPath));
await appendFile(fullPath, contentToAppend, "utf-8");
return {
success: true,
message: `تمت إضافة ${content.length} حرف إلى ${filePath}`,
created: !exists
};
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 4. البحث في الملفات ──
tools.push(
tool({
name: "find",
description: text`🔍 ابحث عن نصوص في الملفات (يدعم Regex)`,
parameters: {
searchPath: z.string().min(1).describe("المسار للبحث فيه"),
pattern: z.string().min(1).describe("النمط للبحث عنه (نص أو Regex)"),
fileTypes: z.array(z.string()).optional().describe("أنواع الملفات للبحث فيها (مثل .ts, .js)"),
maxDepth: z.number().int().min(1).max(10).default(5),
maxResults: z.number().int().min(1).max(50).default(20),
},
implementation: async ({ searchPath, pattern, fileTypes, maxDepth, maxResults }) => {
const results: any[] = [];
let scanned = 0;
const regex = new RegExp(pattern, "i");
const excludeDirs = [
"node_modules", ".git", "dist", "build", "__pycache__",
".venv", "venv", "env", "temp", "tmp"
];
const textExtensions = [
".txt", ".md", ".js", ".jsx", ".ts", ".tsx", ".py",
".json", ".html", ".css", ".csv", ".xml", ".yml",
".yaml", ".sh", ".bash", ".vue", ".svelte", ".go",
".rs", ".rb", ".php"
];
async function scan(dir: string, depth: number = 0) {
if (depth > (maxDepth || 5) || results.length >= (maxResults || 50)) return;
try {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (excludeDirs.includes(entry.name)) continue;
const fullPath = join(dir, entry.name);
try {
if (entry.isDirectory()) {
await scan(fullPath, depth + 1);
} else {
const ext = extname(entry.name).toLowerCase();
if (fileTypes && !fileTypes.includes(ext)) continue;
if (!textExtensions.includes(ext) && !fileTypes) continue;
scanned++;
try {
const content = await readFile(fullPath, "utf-8");
const matches = content.match(regex);
if (matches) {
const lines = content.split("\n");
const lineNum = lines.findIndex((line: string) => regex.test(line)) + 1;
results.push({
file: entry.name,
path: fullPath,
matches: matches.length,
line: lineNum,
preview: lines[lineNum - 1]?.trim().slice(0, 100) || "",
});
}
} catch { /* skip binary files */ }
}
} catch { /* skip */ }
if (results.length >= (maxResults || 50)) return;
}
} catch { /* skip */ }
}
try {
const basePath = resolveInsideBase(searchPath);
if (!existsSync(basePath)) {
return { success: false, error: `المسار غير موجود: ${searchPath}` };
}
await scan(basePath);
return {
success: true,
pattern,
results: results.slice(0, maxResults || 50),
total: results.length,
scanned,
};
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 5. إرسال بريد ──
tools.push(
tool({
name: "sendEmail",
description: text`✉️ إرسال بريد إلكتروني عبر SMTP`,
parameters: {
to: z.string().email().describe("البريد المستلم"),
subject: z.string().min(1).describe("عنوان البريد"),
text: z.string().optional().describe("نص البريد"),
html: z.string().optional().describe("نص البريد بتنسيق HTML"),
},
implementation: async ({ to, subject, text, html }) => {
try {
const response = await fetch("http://localhost:3007/sendEmail", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ to, subject, text, html }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
return { success: true, ...result, system_note: `✉️ تم إرسال البريد إلى ${to}` };
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 6. قراءة البريد ──
tools.push(
tool({
name: "fetchEmails",
description: text`📥 قراءة البريد الإلكتروني عبر IMAP`,
parameters: {
limit: z.number().int().min(1).max(50).default(10),
unreadOnly: z.boolean().default(false),
},
implementation: async ({ limit, unreadOnly }) => {
try {
const response = await fetch("http://localhost:3007/fetchEmails", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ limit, unreadOnly }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
return {
success: true,
...result,
system_note: `📥 تم قراءة ${result.emails?.length || 0} بريداً`
};
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 7. البحث على الإنترنت (مستقل) ──
tools.push(
tool({
name: "searchWeb",
description: text`🌐 البحث على الإنترنت باستخدام Google News`,
parameters: {
query: z.string().min(1).describe("نص البحث"),
maxResults: z.number().int().min(1).max(20).default(5),
},
implementation: searchWebTool,
})
);
// ── 8. تحميل المقالات (مستقل) ──
tools.push(
tool({
name: "fetchArticle",
description: text`📥 تحميل مقال من الإنترنت وحفظه في ملف Markdown`,
parameters: {
url: z.string().url().describe("رابط المقال للتحميل"),
filename: z.string().optional().describe("اسم الملف (اختياري)"),
},
implementation: fetchArticleTool,
})
);
return tools;
}src / toolsProvider.ts
// src/toolsProvider.ts
import { text, tool, type Tool, type ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { spawn } from "child_process";
import {
writeFile,
readFile,
appendFile,
access,
mkdir,
rm,
rename,
cp,
readdir,
stat,
} from "fs/promises";
import { join, dirname, extname, normalize } from "path";
import { existsSync, writeFileSync, mkdirSync } from "fs";
import { findLMStudioHome } from "./findLMStudioHome.js";
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 1. الثوابت والمسارات
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const LMSTUDIO_HOME = findLMStudioHome();
const targetBase = process.env.DEEPEDITOR_BASE || normalize("D:\\bido");
function resolveInsideBase(rel: string): string {
const isAbs = /^([A-Za-z]:)?[\\\/]/.test(rel);
return normalize(isAbs ? rel : join(targetBase, rel));
}
async function ensureDirectory(dirPath: string) {
if (!existsSync(dirPath)) await mkdir(dirPath, { recursive: true });
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 2. دوال الأدوات المستقلة
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ── أداة البحث على الإنترنت (مستقلة) ──
async function searchWebTool(params: {
query: string;
engine?: "duckduckgo" | "google";
maxResults?: number;
}) {
try {
// استيراد serpapi ديناميكياً (لتفادي مشاكل التحميل)
const { getJson } = await import('serpapi');
const API_KEY = 'cd86d518973b79f8e6600672666bff94c590e4b0d2f50be247a2659c03ec1b62';
console.log('🔍 جاري البحث عن: "' + params.query + '" عبر SerpAPI...');
const response = await getJson({
engine: 'google_news',
api_key: API_KEY,
q: params.query,
hl: 'ar',
gl: 'eg',
num: params.maxResults || 5
});
let results: any[] = [];
if (response.news_results && response.news_results.length > 0) {
for (let i = 0; i < response.news_results.length && i < (params.maxResults || 5); i++) {
const item = response.news_results[i];
if (!item.link || item.link === '#') continue;
let source = 'Google News';
if (item.source) {
if (typeof item.source === 'string') source = item.source;
else if (item.source.name) source = item.source.name;
}
let snippet = item.snippet || item.description || item.summary || '';
if (typeof snippet !== 'string') snippet = JSON.stringify(snippet);
if (!snippet || snippet === 'لا يوجد وصف' || snippet.trim() === '') {
snippet = item.title || 'لا يوجد وصف';
}
let published = item.date || new Date().toISOString();
if (typeof published !== 'string') published = JSON.stringify(published);
let title = item.title || 'بدون عنوان';
title = title.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
results.push({ title, url: item.link, snippet, source, published });
}
}
// إذا لم تكن هناك نتائج عربية، جرب الإنجليزية
if (results.length === 0) {
console.log('⚠️ لا توجد نتائج عربية، جاري البحث بالإنجليزية...');
const cleanQuery = params.query.replace(/[^\w\s]/g, '').trim();
const englishResponse = await getJson({
engine: 'google_news',
api_key: API_KEY,
q: cleanQuery || 'artificial intelligence',
hl: 'en',
gl: 'us',
num: params.maxResults || 5
});
if (englishResponse.news_results) {
for (let j = 0; j < englishResponse.news_results.length && j < (params.maxResults || 5); j++) {
const item = englishResponse.news_results[j];
if (!item.link || item.link === '#') continue;
let source = 'Google News';
if (item.source) {
if (typeof item.source === 'string') source = item.source;
else if (item.source.name) source = item.source.name;
}
let snippet = item.snippet || item.description || item.summary || '';
if (typeof snippet !== 'string') snippet = JSON.stringify(snippet);
if (!snippet || snippet === 'لا يوجد وصف' || snippet.trim() === '') {
snippet = item.title || 'لا يوجد وصف';
}
let published = item.date || new Date().toISOString();
if (typeof published !== 'string') published = JSON.stringify(published);
let title = item.title || 'بدون عنوان';
title = title.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
results.push({ title, url: item.link, snippet, source, published });
}
}
}
if (results.length === 0) {
return {
success: false,
error: 'لم يتم العثور على نتائج',
system_note: '🌐 تم البحث عن "' + params.query + '" في google'
};
}
let displayLines = ['🌐 نتائج البحث عن: ' + params.query];
for (let i = 0; i < results.length; i++) {
const r = results[i];
displayLines.push(
'\n' + (i+1) + '. ' + r.title,
' 📰 ' + r.source + ' | 📅 ' + r.published,
' 📄 ' + r.snippet,
' 🔗 ' + r.url
);
}
return {
success: true,
results: results,
display: displayLines.join('\n'),
total: results.length,
system_note: '🌐 تم البحث عن "' + params.query + '" في google'
};
} catch (error: any) {
console.error('❌ خطأ في البحث:', error.message);
return {
success: false,
error: 'فشل البحث: ' + error.message,
system_note: '⚠️ فشل البحث: ' + error.message
};
}
}
// ── أداة تحميل المقالات (مستقلة) ──
async function fetchArticleTool(params: {
url: string;
filename?: string;
}) {
try {
// استيراد node-fetch ديناميكياً
const fetch = (await import('node-fetch')).default;
console.log('📥 جاري تحميل المقال من: ' + params.url);
const response = await fetch(params.url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const html = await response.text();
// استخراج العنوان
let title = 'بدون عنوان';
const titleMatch = html.match(/<title>(.*?)<\/title>/);
if (titleMatch) {
title = titleMatch[1].replace(/<[^>]*>/g, '').trim();
}
// تنظيف النص
let textContent = html
.replace(/<script[\s\S]*?<\/script>/g, ' ')
.replace(/<style[\s\S]*?<\/style>/g, ' ')
.replace(/<[^>]*>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
// اختصار النص
const maxChars = 5000;
let content = textContent;
if (content.length > maxChars) {
content = content.substring(0, maxChars) + '\n\n... (المقال مختصر)';
}
// إنشاء اسم الملف
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const safeTitle = title.replace(/[^a-zA-Z0-9\u0600-\u06FF]/g, '_').slice(0, 50);
const fileName = params.filename || safeTitle + '_' + timestamp;
const saveDir = join(targetBase, 'internet_search_results');
if (!existsSync(saveDir)) {
mkdirSync(saveDir, { recursive: true });
}
const filePath = join(saveDir, fileName + '.md');
const markdownContent = '# ' + title + '\n\n' +
'**المصدر:** ' + params.url + '\n' +
'**تاريخ التحميل:** ' + new Date().toLocaleString('ar-EG') + '\n\n' +
'---\n\n' +
content;
writeFileSync(filePath, markdownContent, 'utf-8');
console.log('✅ تم حفظ المقال في: ' + filePath);
return {
success: true,
message: '✅ تم حفظ المقال في: ' + filePath,
file: filePath,
title: title,
wordCount: textContent.split(/\s+/).length,
charCount: textContent.length
};
} catch (error: any) {
console.error('❌ خطأ في تحميل المقال:', error.message);
return {
success: false,
error: 'فشل تحميل المقال: ' + error.message
};
}
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 3. التسجيل النهائي في LM Studio
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
export async function toolsProvider(ctl: ToolsProviderController): Promise<Tool[]> {
const tools: Tool[] = [];
// ── 1. كتابة ملف متقدم ──
tools.push(
tool({
name: "writeFileadvanced",
description: text`اكتب محتوى إلى ملف (ينشئ المجلدات تلقائياً)`,
parameters: {
file: z.string().min(1).describe("مسار الملف"),
content: z.string().describe("المحتوى المراد كتابته"),
},
implementation: async ({ file, content }) => {
try {
const path = resolveInsideBase(file);
await ensureDirectory(dirname(path));
await writeFile(path, content, "utf-8");
return { success: true, message: `تم كتابة الملف ${file}` };
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 2. قراءة ملف متقدم ──
tools.push(
tool({
name: "readFileadvanced",
description: text`اقرأ محتوى ملف نصي`,
parameters: {
file: z.string().min(1).describe("مسار الملف"),
},
implementation: async ({ file }) => {
try {
const path = resolveInsideBase(file);
if (!existsSync(path)) {
return { success: false, error: `الملف غير موجود: ${file}` };
}
const content = await readFile(path, "utf-8");
return { success: true, content, size: content.length };
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 3. الإضافة إلى ملف ──
tools.push(
tool({
name: "appendToFile",
description: text`📝 أضف محتوى إلى نهاية ملف (ينشئ الملف إذا لم يكن موجوداً)`,
parameters: {
filePath: z.string().min(1).describe("مسار الملف"),
content: z.string().min(1).describe("المحتوى المراد إضافته"),
createIfNotExists: z.boolean().default(true).describe("إنشاء الملف إذا لم يكن موجوداً"),
addNewLine: z.boolean().default(true).describe("إضافة سطر جديد قبل المحتوى"),
},
implementation: async ({ filePath, content, createIfNotExists, addNewLine }) => {
try {
const fullPath = resolveInsideBase(filePath);
const exists = await access(fullPath).then(() => true).catch(() => false);
if (!exists && !createIfNotExists) {
return {
success: false,
error: `الملف غير موجود: ${filePath}`,
hint: "استخدم createIfNotExists=true لإنشائه"
};
}
let contentToAppend = content;
if (addNewLine !== false) {
contentToAppend = "\n" + contentToAppend;
}
await ensureDirectory(dirname(fullPath));
await appendFile(fullPath, contentToAppend, "utf-8");
return {
success: true,
message: `تمت إضافة ${content.length} حرف إلى ${filePath}`,
created: !exists
};
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 4. البحث في الملفات ──
tools.push(
tool({
name: "find",
description: text`🔍 ابحث عن نصوص في الملفات (يدعم Regex)`,
parameters: {
searchPath: z.string().min(1).describe("المسار للبحث فيه"),
pattern: z.string().min(1).describe("النمط للبحث عنه (نص أو Regex)"),
fileTypes: z.array(z.string()).optional().describe("أنواع الملفات للبحث فيها (مثل .ts, .js)"),
maxDepth: z.number().int().min(1).max(10).default(5),
maxResults: z.number().int().min(1).max(50).default(20),
},
implementation: async ({ searchPath, pattern, fileTypes, maxDepth, maxResults }) => {
const results: any[] = [];
let scanned = 0;
const regex = new RegExp(pattern, "i");
const excludeDirs = [
"node_modules", ".git", "dist", "build", "__pycache__",
".venv", "venv", "env", "temp", "tmp"
];
const textExtensions = [
".txt", ".md", ".js", ".jsx", ".ts", ".tsx", ".py",
".json", ".html", ".css", ".csv", ".xml", ".yml",
".yaml", ".sh", ".bash", ".vue", ".svelte", ".go",
".rs", ".rb", ".php"
];
async function scan(dir: string, depth: number = 0) {
if (depth > (maxDepth || 5) || results.length >= (maxResults || 50)) return;
try {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (excludeDirs.includes(entry.name)) continue;
const fullPath = join(dir, entry.name);
try {
if (entry.isDirectory()) {
await scan(fullPath, depth + 1);
} else {
const ext = extname(entry.name).toLowerCase();
if (fileTypes && !fileTypes.includes(ext)) continue;
if (!textExtensions.includes(ext) && !fileTypes) continue;
scanned++;
try {
const content = await readFile(fullPath, "utf-8");
const matches = content.match(regex);
if (matches) {
const lines = content.split("\n");
const lineNum = lines.findIndex((line: string) => regex.test(line)) + 1;
results.push({
file: entry.name,
path: fullPath,
matches: matches.length,
line: lineNum,
preview: lines[lineNum - 1]?.trim().slice(0, 100) || "",
});
}
} catch { /* skip binary files */ }
}
} catch { /* skip */ }
if (results.length >= (maxResults || 50)) return;
}
} catch { /* skip */ }
}
try {
const basePath = resolveInsideBase(searchPath);
if (!existsSync(basePath)) {
return { success: false, error: `المسار غير موجود: ${searchPath}` };
}
await scan(basePath);
return {
success: true,
pattern,
results: results.slice(0, maxResults || 50),
total: results.length,
scanned,
};
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 5. إرسال بريد ──
tools.push(
tool({
name: "sendEmail",
description: text`✉️ إرسال بريد إلكتروني عبر SMTP`,
parameters: {
to: z.string().email().describe("البريد المستلم"),
subject: z.string().min(1).describe("عنوان البريد"),
text: z.string().optional().describe("نص البريد"),
html: z.string().optional().describe("نص البريد بتنسيق HTML"),
},
implementation: async ({ to, subject, text, html }) => {
try {
const response = await fetch("http://localhost:3007/sendEmail", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ to, subject, text, html }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
return { success: true, ...result, system_note: `✉️ تم إرسال البريد إلى ${to}` };
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 6. قراءة البريد ──
tools.push(
tool({
name: "fetchEmails",
description: text`📥 قراءة البريد الإلكتروني عبر IMAP`,
parameters: {
limit: z.number().int().min(1).max(50).default(10),
unreadOnly: z.boolean().default(false),
},
implementation: async ({ limit, unreadOnly }) => {
try {
const response = await fetch("http://localhost:3007/fetchEmails", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ limit, unreadOnly }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
return {
success: true,
...result,
system_note: `📥 تم قراءة ${result.emails?.length || 0} بريداً`
};
} catch (error: any) {
return { success: false, error: error.message };
}
},
})
);
// ── 7. البحث على الإنترنت (مستقل) ──
tools.push(
tool({
name: "searchWeb",
description: text`🌐 البحث على الإنترنت باستخدام Google News`,
parameters: {
query: z.string().min(1).describe("نص البحث"),
maxResults: z.number().int().min(1).max(20).default(5),
},
implementation: searchWebTool,
})
);
// ── 8. تحميل المقالات (مستقل) ──
tools.push(
tool({
name: "fetchArticle",
description: text`📥 تحميل مقال من الإنترنت وحفظه في ملف Markdown`,
parameters: {
url: z.string().url().describe("رابط المقال للتحميل"),
filename: z.string().optional().describe("اسم الملف (اختياري)"),
},
implementation: fetchArticleTool,
})
);
return tools;
}