ده كود الصفحة كامل بنفس الثيم، وفيه:
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، المنصات المستخدمة، وأوامر التنفيذ الكاملة
الأمر طويل لذلك ظاهر منه جزء صغير فقط. يمكنك نسخه مباشرة أو عرضه كاملاً.
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
أدوات إضافية مفيدة للمساعد
موارد إضافية يمكن استخدامها لإضافة قدرات وأدوات جديدة للمساعد