ده كود الصفحة كامل بنفس الثيم، وفيه: 1. عنوان **الموسم الأردني للذكاء الاصطناعي "JAIS 2026"** 2. رابط منصة OpenCode 3. رابط منصة Groq 4. قسم **أوامر بناء المساعد** 5. الأمر الأول `Prompt 1 Foundation` مخفي جزئيا 6. الأمر الثاني `Prompt 2 Video Editing Pipeline` مخفي جزئيا 7. أزرار نسخ لكل أمر 8. أزرار عرض وإخفاء 9. رابط Playwright MCP 10. نفس التصميم والحركة والـ spotlight والثيم السابق ````html الموسم الأردني للذكاء الاصطناعي JAIS 2026 | Arabian AI School
Arabian AI School

الموسم الأردني للذكاء الاصطناعي "JAIS 2026"

ملحقات بناء المساعد الذكي BaraaClaw، المنصات المستخدمة، وأوامر التنفيذ الكاملة

مدرسة الذكاء الاصطناعي

Official Channels
MAIN PLATFORMS

المنصات الأساسية المستخدمة

المنصات التي تحتاجها لبناء وتشغيل المساعد الذكي

COMMAND 1 · FOUNDATION

الأمر الأول لبناء أساس المساعد

Prompt 1

الأمر طويل لذلك ظاهر منه جزء صغير فقط. يمكنك نسخه مباشرة أو عرضه كاملاً.

You are building **BaraaClaw**, a personal AI agent from scratch. TypeScript, ES modules, runs locally, Telegram-only interface. No web server. No forks of anything. Generate ALL files. Every file must be complete. Do not leave placeholders or TODOs. ## Stack (exact packages) package.json must use: - "type": "module" - grammy (Telegram bot, long polling) - groq-sdk (primary LLM — Llama 3.3 70B free tier) - @google/generative-ai (Gemini fallback LLM) - better-sqlite3 (persistent memory) - dotenv (env loading) - tsx (dev runner) - TypeScript with strict mode, ES2022 target, NodeNext module resolution tsconfig.json: strict: true, noUncheckedIndexedAccess: true, outDir: "dist", rootDir: "src", sourceMap: true. Scripts: "dev": "tsx watch src/index.ts" "build": "tsc -p tsconfig.json" "start": "node dist/index.js" "typecheck": "tsc --noEmit" ## Architecture — exact file tree src/ config/env.ts utils/logger.ts memory/db.ts memory/memoryStore.ts agent/ agent.ts llm/types.ts llm/groqProvider.ts llm/geminiProvider.ts llm/index.ts tools/types.ts tools/getCurrentTime.ts tools/index.ts bot/whitelist.ts bot/bot.ts index.ts ## Detailed specs per file ### src/config/env.ts - import 'dotenv/config' at top - `required(name)` helper: throws if env var is missing or starts with "REPLACE_WITH" - Parse TELEGRAM_ALLOWED_USER_IDS as comma-separated numbers, throw if empty - GEMINI_API_KEY is optional: if missing or starts with REPLACE_WITH, set to undefined - Export a single `config` object with these fields and defaults: - telegramBotToken: required - allowedUserIds: number[] - groqApiKey: required - groqModel: default "llama-3.3-70b-versatile" - geminiApiKey: string | undefined - geminiModel: default "gemini-1.5-flash" - dbPath: default "./memory.db" - agentMaxIterations: default 6 ### src/utils/logger.ts - Export `logger` with .info, .warn, .error methods - Each prepends ISO timestamp and log level ### src/memory/db.ts - Create Database instance from better-sqlite3 using config.dbPath - Set WAL journal mode - Create `messages` table: id INTEGER PRIMARY KEY AUTOINCREMENT, chat_id INTEGER NOT NULL, role TEXT NOT NULL, content TEXT, tool_calls_json TEXT, tool_call_id TEXT, tool_name TEXT, created_at TEXT DEFAULT datetime('now') - Index on chat_id - Export the db instance ### src/memory/memoryStore.ts - Import db and ChatMessage/ToolCall types - appendMessage(chatId, role, content, toolCalls?, toolCallId?, toolName?): uses a prepared INSERT - getHistory(chatId, limit): SELECT ordered by id DESC with LIMIT, then .reverse(). Returns ChatMessage[] - clearHistory(chatId): DELETE WHERE chat_id = ? ### src/agent/llm/types.ts — exact type definitions ```typescript export type Role = 'system' | 'user' | 'assistant' | 'tool'; export interface ToolCall { id: string; name: string; arguments: string; // JSON-encoded } export interface ChatMessage { role: Role; content: string | null; toolCalls?: ToolCall[]; toolCallId?: string; name?: string; } export interface JsonSchema { type: 'object' | 'string' | 'number' | 'integer' | 'boolean' | 'array'; properties?: Record; items?: JsonSchema; description?: string; enum?: unknown[]; required?: string[]; } export interface ToolDefinition { name: string; description: string; parameters: JsonSchema; } export interface LlmProvider { readonly name: string; generate(messages: ChatMessage[], tools: ToolDefinition[]): Promise; } ```` ### src/agent/llm/groqProvider.ts Class implementing LlmProvider Uses Groq SDK client generate(): calls groq.chat.completions.create with model, messages, tools (OpenAI function-calling format) Converts internal ChatMessage to Groq format: tool role messages need tool_call_id assistant tool_calls need id/type/function structure Returns ChatMessage with extracted content and toolCalls ### src/agent/llm/geminiProvider.ts Class implementing LlmProvider Uses GoogleGenerativeAI from @google/generative-ai Extracts system messages into systemInstruction string Converts tools to Gemini FunctionDeclaration format using SchemaType enum mapping Maps roles: assistant→model tool→function with functionResponse parts Cast tools as never to handle SDK type strictness Cast response parts to: Array<{ text?: string; functionCall?: { name: string; args?: Record } }> Generate tool call IDs as: gemini-call-${Date.now()}-${idx} ### src/agent/llm/index.ts createLlm(): creates GroqProvider as primary GeminiProvider as fallback only if geminiApiKey exists Returns an LlmProvider that: tries Groq first catches any error falls back to Gemini Re-exports all types from types.ts ### src/agent/tools/types.ts export interface ToolContext { chatId: number; sendProgress?: (text: string) => Promise; } export interface Tool { definition: ToolDefinition; execute(args: Record, ctx: ToolContext): Promise; } ### src/agent/tools/getCurrentTime.ts Tool named "get_current_time" Optional "timezone" parameter IANA timezone string Uses Intl.DateTimeFormat with: dateStyle: 'full' timeStyle: 'long' Returns JSON with: iso formatted timezone Catches invalid timezone and returns error JSON ### src/agent/tools/index.ts Registry array of Tool objects getToolDefinitions(): returns definitions executeTool(name, argumentsJson, ctx): finds tool parses JSON args executes with try/catch returns JSON error strings on failure ### src/agent/agent.ts System prompt: "You are BaraaClaw, a personal AI agent running locally..." mentions: tools conciseness video editing capability createLlm() at module level RunAgentOptions interface: { sendProgress?, userId? } runAgent(chatId, userText, options?): Append user message to memory Load history limit 30 Build messages array: system prompt + history Loop up to agentMaxIterations Call llm.generate If no tool calls: save return content If tool calls: save assistant message execute each tool save tool results On max iterations: return safety message Pass sendProgress and chatId through ToolContext to tools ### src/bot/whitelist.ts isAllowedUser(userId): checks config.allowedUserIds.includes ### src/bot/bot.ts createBot(): creates grammy Bot Middleware: check whitelist before any handler silently drop unauthorized /start command: reply with online message /reset command: clearHistory + reply message:text handler: replyWithChatAction('typing') call runAgent reply with result bot.catch for unhandled errors All ctx.reply calls wrapped in try/catch via a safeSend helper ### src/index.ts Import createBot and db Start bot with long polling log username on start Graceful shutdown on SIGINT/SIGTERM: bot.stop() db.close() ### .env.example Include all config vars with "REPLACE_WITH_YOURS" placeholders and comments explaining each. ### .env Copy of .env.example user will fill in real values ### .gitignore node_modules/ dist/ .env *.db *.db-journal *.db-wal .db-shm service-account.json npm-debug.log .DS_Store EDITED-VIDEO/ tmp/ ## CRITICAL RULES Every import must use .js extension: import { x } from './foo.js' No comments in code except the ones specified above Use prepared statements for all SQLite queries Never use shell string concatenation for subprocess commands All error messages must be clear and actionable The bot must fail fast on startup if secrets are missing Run: npm install npm run typecheck npm run build Verify zero errors before reporting done
```
Copy ready
COMMAND 2 · VIDEO EDITING PIPELINE

الأمر الثاني لإضافة تعديل الفيديو

Prompt 2

هذا الأمر يضيف Pipeline كاملة لتحليل الفيديو وتفريغه وحذف الصمت واللقطات غير المطلوبة.

``` I have an existing BaraaClaw project — a TypeScript Telegram bot agent with this structure: src/ config/env.ts — config object with all env vars, required() helper utils/logger.ts — timestamped logger memory/db.ts — SQLite (better-sqlite3, WAL), exports `db` instance memory/memoryStore.ts — append/read/clear conversation history agent/ agent.ts — runAgent(chatId, userText, options?) with RunAgentOptions { sendProgress?, userId? } llm/types.ts — ChatMessage, ToolCall, ToolDefinition, LlmProvider, JsonSchema, Role llm/groqProvider.ts — Groq LLM llm/geminiProvider.ts — Gemini LLM fallback llm/index.ts — createLlm() returns provider with Groq→Gemini fallback tools/types.ts — Tool interface, ToolContext { chatId, sendProgress? } tools/getCurrentTime.ts tools/index.ts — registry array, getToolDefinitions(), executeTool() bot/ whitelist.ts — isAllowedUser(userId) bot.ts — grammy Bot, whitelist middleware, /start, /reset, message:text handler index.ts — entrypoint, graceful shutdown Do NOT modify or recreate any file unless listed below. Do NOT break any existing functionality. Add a complete video editing feature. Generate ALL listed files in full. No placeholders. No TODOs. ## New files to create src/services/types.ts src/services/media.ts src/services/transcription.ts src/services/silenceDetector.ts src/services/outtakeDetector.ts src/services/editPlanBuilder.ts src/services/videoEditor.ts src/bot/pendingJobs.ts src/agent/tools/editVideo.ts ## Existing files to modify src/config/env.ts src/memory/db.ts src/agent/tools/index.ts src/agent/agent.ts src/bot/bot.ts .env.example .gitignore ## Config additions Add these fields to the existing config object: groqTranscriptionModel: default "whisper-large-v3" videoEditOutputDir: default "./EDITED-VIDEO" videoEditTempDir: default "./tmp/video-jobs" videoEditMaxInputMb: default 500 videoEditMinAiConfidence: default 0.92 videoEditMaxAiRemovalPercent: default 20 videoEditMaxSingleAiCutSeconds: default 30 videoSilenceThresholdDb: default -40 videoMinSilenceDuration: default 0.8 videoKeepSilencePadding: default 0.15 videoJobTtlMinutes: default 30 ## Database Add this table to the existing db.exec block: CREATE TABLE IF NOT EXISTS pending_video_jobs ( job_id TEXT PRIMARY KEY, chat_id INTEGER NOT NULL, user_id INTEGER NOT NULL, file_path TEXT NOT NULL, original_filename TEXT NOT NULL, file_size_bytes INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_pvj_user_status ON pending_video_jobs(user_id, status); ## src/services/types.ts export interface ProbeResult { duration: number; hasVideo: boolean; hasAudio: boolean; width: number; height: number; videoCodec: string | null; audioCodec: string | null; } export interface TranscriptSegment { start: number; end: number; text: string; } export interface TranscriptWord { word: string; start: number; end: number; } export interface Transcript { segments: TranscriptSegment[]; words: TranscriptWord[]; text: string; language: string; duration: number; } export interface RemovalCandidate { start: number; end: number; category: | 'false_start' | 'self_correction' | 'repeated_take' | 'production_remark' | 'broken_phrase'; confidence: number; reason: string; } export interface SilenceInterval { start: number; end: number; duration: number; } export interface KeepInterval { start: number; end: number; } export interface RejectedRemoval { candidate: RemovalCandidate; reason: string; } export interface EditPlan { candidates: RemovalCandidate[]; acceptedRemovals: RemovalCandidate[]; rejectedRemovals: RejectedRemoval[]; silenceIntervals: SilenceInterval[]; allRemovalIntervals: Array<{ start: number; end: number; source: string; }>; keepIntervals: KeepInterval[]; originalDuration: number; editedDuration: number; removedDuration: number; config: Record; } export interface PendingVideoJob { jobId: string; chatId: number; userId: number; filePath: string; originalFilename: string; fileSizeBytes: number; createdAt: string; status: 'pending' | 'processing'; } ## src/services/media.ts FFmpeg/FFprobe wrapper. ALL subprocess calls must use execFile with argument arrays from node:child_process. NEVER shell strings. 10-minute timeout on all processes. Functions to export: ensureFfmpeg() Checks ffmpeg and ffprobe are on PATH. Throws clear install guidance if missing. Caches result after first success. probeMedia(filePath) → ProbeResult Runs ffprobe with: -print_format json -show_format -show_streams Parses JSON output. extractAudio(inputPath, outputPath) ffmpeg extract mono 16kHz FLAC audio. Args: -y -i input -vn -ac 1 -ar 16000 -c:a flac output splitAudioChunk( inputPath, outputPath, startSec, durationSec ) ffmpeg extract audio chunk as FLAC. cutSegment( inputPath, outputPath, startSec, endSec ) ffmpeg extract video segment. Args: -y -ss start -to end -i input -c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p -c:a aac -b:a 128k -avoid_negative_ts make_zero output concatSegments( concatFilePath, outputPath ) ffmpeg concat demuxer. Args: -y -f concat -safe 0 -i concatFile -c copy -movflags +faststart output detectSilenceRaw( inputPath, thresholdDb, minDuration ) → string Runs ffmpeg silencedetect filter. Returns stderr. Catch the error and return stderr if it contains "silence_start". getFileSizeBytes(filePath) ensureDir(dir) safeUnlink(filePath) ## src/services/transcription.ts Groq Whisper transcription with chunking for large files. GROQ_MAX_UPLOAD_BYTES = 24 * 1024 * 1024 CHUNK_OVERLAP_SECONDS = 5 transcribeAudio(audioPath, jobDir) → Transcript Check file size. If ≤ limit: transcribeSingle() If > limit: transcribeChunked() Calculate chunk duration from file size and audio duration. Split with splitAudioChunk. Transcribe each sequentially. Merge. Save: transcript.raw.json transcript.normalized.json in jobDir. transcribeSingle(groq, audioPath): groq.audio.transcriptions.create with: response_format: 'verbose_json' timestamp_granularities: ['segment', 'word'] Cast params as never for type compatibility. transcribeChunked( groq, audioPath, jobDir, fileSize ) Split into overlapping chunks. Transcribe each. Merge with timestamp remapping. Merge logic: Offset all timestamps by chunk global start time. Skip segments whose global start overlaps with previously added content. Deduplication thresholds: 0.5s segments 0.3s words normalizeTranscript(raw): trim text round timestamps to 3 decimal places ## src/services/silenceDetector.ts detectSilence( inputPath, videoDuration ) → SilenceInterval[] Calls detectSilenceRaw from media.ts. Parse stderr with regex for: silence_start: silence_end: silence_duration: Apply padding: paddedStart = start + config.videoKeepSilencePadding paddedEnd = end - config.videoKeepSilencePadding Skip if paddedStart >= paddedEnd. Clamp to [0, videoDuration]. ## src/services/outtakeDetector.ts detectOuttakes( segments: TranscriptSegment[] ) → RemovalCandidate[] Uses createLlm() from: agent/llm/index.ts NOT a separate provider. System prompt instructs: only high-confidence removals categories: false_start self_correction repeated_take production_remark broken_phrase respond with ONLY a JSON array each element must have: start end category confidence reason Formats transcript as: [M:SS.s -> M:SS.s] text per segment. Calls: llm.generate(messages, []) with no tools. Extracts JSON from response. Handles: markdown code blocks bare arrays Validates each candidate: correct types start >= 0 end > start category in allowed set confidence 0-1 If JSON parsing fails or response malformed: return empty array Graceful degradation. Log warning. ## src/services/editPlanBuilder.ts buildEditPlan( candidates, silenceIntervals, videoDuration ) → EditPlan Filter candidates into: accepted rejected Reject if: confidence < config.videoEditMinAiConfidence Reject if: timestamps out of [0, duration] Reject if: cut duration > config.videoEditMaxSingleAiCutSeconds Reject if: total AI removal would exceed config.videoEditMaxAiRemovalPercent percent of video Record rejection reason for each. Merge all removal intervals: accepted AI + silence Sort by start time. Merge overlapping intervals. If: current.start <= last.end extend last.end. Clamp all to: [0, duration] Invert to compute keep intervals. Walk from 0. For each removal gap create a keep interval. Filter out keeps shorter than 0.05s. Calculate: editedDuration removedDuration ## src/services/videoEditor.ts Orchestrator. Export VideoEditResult interface: outputPath outputFilename originalDuration editedDuration removedDuration plan editVideo( inputPath, originalFilename, jobDir, sendProgress ) → VideoEditResult ensureFfmpeg() probeMedia Check: hasVideo hasAudio duration >= 1 sendProgress("Extracting audio...") extractAudio to: jobDir/audio.flac sendProgress("Transcribing audio...") transcribeAudio sendProgress("Analyzing transcript...") Run in parallel: detectOuttakes detectSilence using Promise.all buildEditPlan Save: edit-plan.json in jobDir Abort if: no keep intervals or: removedDuration < 0.5 sendProgress: "Cutting video: keeping N segments, removing Xs..." For each keep interval: cutSegment to: jobDir/segments/seg_NNNN.mp4 If only 1 segment: rename to output If multiple: write concat.txt concatSegments Output path: config.videoEditOutputDir/ edited__.mp4 Sanitize filename: replace dangerous chars with _ limit to 80 chars ## src/bot/pendingJobs.ts SQLite-backed pending job store using existing db instance. Functions: createPendingJob( chatId, userId, filePath, originalFilename, fileSizeBytes ) → PendingVideoJob Generates UUID. Inserts row. getPendingJobForUser(userId) → PendingVideoJob | undefined Finds most recent pending job within TTL. getJobById(jobId) → PendingVideoJob | undefined markJobProcessing(jobId) markJobPending(jobId) deleteJob(jobId) purgeExpiredJobs() Deletes rows older than TTL. TTL query: WHERE created_at > datetime('now', '-N minutes') using: config.videoJobTtlMinutes All queries use prepared statements. ## src/agent/tools/editVideo.ts Tool named: "edit_video" One required parameter: jobId string execute(): Validate jobId is non-empty string. getJobById. Return error JSON if not found. If status is processing: return error JSON. markJobProcessing. Call editVideo service in try/catch. On success: deleteJob format summary sendProgress(summary) return success JSON On failure: markJobPending return error JSON In finally: rm -rf jobDir recursive force ignore errors Summary format: Video editing complete! Original file Output file Output path Original duration Final duration Total removed AI outtake cuts count Silence cuts count Skipped safety count if > 0 ## src/agent/agent.ts modifications Import: getPendingJobForUser purgeExpiredJobs from: bot/pendingJobs At start of runAgent: call purgeExpiredJobs() After building systemContent from SYSTEM_PROMPT: check if options.userId exists. If so: call getPendingJobForUser(options.userId) If pending job exists, append: [Context: The user has a pending video upload waiting to be processed. Job ID: File: Received: minute(s) ago. If the user is asking to edit, produce, clean up, or process this video, call the edit_video tool with jobId "".] When calling executeTool pass: { chatId, sendProgress: options?.sendProgress } as ToolContext. ## src/bot/bot.ts modifications Replace entire file. Keep ALL existing functionality: whitelist middleware /start /reset message:text Add: safeSend(ctx, text) Wraps ctx.reply in try/catch. Logs warning on failure. Use for ALL message sending. runAgentWithProgress( ctx, chatId, userId, text ) Creates sendProgress callback wrapping safeSend. Calls runAgent with: { sendProgress, userId } safeSends reply. message:video handler: handleVideoUpload( ctx, ctx.message.video ) message:document handler: if mime_type starts with: video/ handleVideoUpload otherwise: treat caption as text message:photo handler: reply: "I can only edit MP4 video files right now." If caption exists: run it as text message:text handler: use handleTextFallback which calls: runAgentWithProgress handleVideoUpload(ctx, fileInfo): Check file_size against: config.videoEditMaxInputMb Check file_size against: 20MB Telegram Bot API download limit If exceeded: explain limitation Download file: ctx.api.getFile(file_id) get file_path download from: [https://api.telegram.org/file/bot<token>/<file_path](https://api.telegram.org/file/bot<token>/<file_path)> Save to: config.videoEditTempDir/ / input. createPendingJob If caption exists: run agent with caption text If no caption: reply asking what to do File download: use node:https.get with: node:stream/promises pipeline to: createWriteStream Handle one redirect level. Generate collision-safe path with: randomUUID Filename sanitization: replace: <>:"/|?* and control chars with _ limit to 120 chars. ## .gitignore additions Add: EDITED-VIDEO/ tmp/ ## CRITICAL RULES Every import uses .js extension. All ffmpeg/ffprobe calls use execFile with argument arrays. NEVER shell strings. All Telegram messages are plain text. NEVER use parse_mode. All ctx.reply calls go through safeSend. The edit_video tool only accepts jobId. The LLM never supplies filesystem paths. Sanitize all filenames. Prevent path traversal. Temp files cleaned up in finally blocks. Run: npm run typecheck npm run build Must produce ZERO errors. Do not add any npm packages beyond what's already in the project.
```
Copy ready
EXTRA TOOLS

أدوات إضافية مفيدة للمساعد

موارد إضافية يمكن استخدامها لإضافة قدرات وأدوات جديدة للمساعد

QUICK START

افتح OpenCode، نفذ الأمر الأول لبناء الأساس، وبعد اكتماله استخدم الأمر الثاني لإضافة قدرات تعديل الفيديو

استخدم Groq لتشغيل الموديل والـ API، ثم أضف أدوات MCP حسب احتياجات المساعد.

```
```