chore(fixtures): enrichit les vaults de test (frontmatter complet, types fichiers pour vues/aper/us, dossiers avec accents+espaces, scripts dupliques, images/logs) + purge residus e2e-diagram
CI / lint (push) Successful in 1m55s
CI / security (push) Successful in 1m20s
CI / test (push) Successful in 3m43s
CI / build (push) Successful in 1m16s
CI / e2e (push) Successful in 11m44s

This commit is contained in:
2026-09-22 13:42:49 -04:00
parent ab7c227b97
commit 6f0a6f7fd8
39 changed files with 1583 additions and 1955 deletions
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
Voici les trois niveaux de résumé pour le document **"Agents IA — Panorama Complet 2026"** :
---
---
### **1. En une phrase**
Ce document est **un panorama exhaustif et structuré des 72 agents IA disponibles en 2026**, classés par catégories, avec leurs descriptions, statuts, installations, utilisations et comparatifs techniques.
---
---
### **2. En un paragraphe**
Le document **"Agents IA — Panorama Complet 2026"** est une **référence complète** pour comprendre, installer et utiliser les agents IA du marché. Il recense **72 agents** répartis en **16 catégories** (ex. : agents locaux, poids lourds open source, trésors open source, local-first, propriétaires, etc.). Chaque agent est détaillé avec sa **description**, ses **liens officiels** (page principale et documentation), son **statut** (actif, stagnant ou fermé), ses **étapes d'installation** (commandes précises) et son **mode d'utilisation** (exemples de commandes). Le document inclut également un **tableau récapitulatif comparatif** (stars GitHub, type, compatibilité locale, etc.), des **recommandations**, une **matrice de décision**, ainsi que des sections dédiées aux **benchmarks**, **coûts**, **sécurité** et **glossaire**. Les sources sont vérifiées et mises à jour en **août 2026**.
---
---
### **3. En une page**
---
#### **Contexte**
Le document **"Agents IA — Panorama Complet 2026"**, rédigé par **Bruno Charest**, est une **ressource de référence** pour les développeurs, chercheurs et utilisateurs souhaitant explorer l’écosystème des agents IA. Il s’appuie sur des sources officielles (GitHub, arXiv, blogs spécialisés) et est **mis à jour au 12 août 2026**. L’objectif est de fournir une **vue d’ensemble structurée** des agents disponibles, leurs fonctionnalités, et leurs spécificités techniques.
---
#### **Structure et contenu**
Le document est organisé en **23 sections**, dont :
- **Tableau récapitulatif comparatif** : Une vue synthétique des 72 agents, avec des colonnes pour le **nom**, la **catégorie**, le **type** (OSS, propriétaire, etc.), le **nombre d’étoiles GitHub**, le **statut** (🟢 actif, 🟡 stagnant, 🔴 fermé), la **compatibilité locale** (✅/~/❌), et les **méthodes d’installation**.
- **Catégories d’agents** :
- **Agents installés localement** (ex. : Claude Code, Hermes Agent, Atomic Agent, Pi, Tiny Agents, NemoClaw).
- **Prime Agent** : Agent open-source basé sur le modèle **RLM** (Recursive Language Model), avec des fonctionnalités avancées comme la mémoire persistante et l’auto-amélioration.
- **Poids lourds open source** (ex. : OpenCode, Claw Code, Gemini CLI, Codex CLI, OpenHands, Goose, Aider).
- **Trésors open source** : Agents optimisés pour des cas d’usage spécifiques (ex. : jcode en Rust, agentty en C++26, NullClaw en Zig).
- **Agents local-first** : Solutions conçues pour fonctionner **100 % localement** (ex. : openyak, Kun, iPolloWork).
- **Écosystème OpenClaw** : Une famille d’agents légers et sécurisés (ex. : OpenClaw, ZeroClaw, NanoClaw).
- **Agents propriétaires** : Outils fermés mais populaires (ex. : Cursor CLI, Warp, GitHub Copilot CLI, Devin).
- **Sections transverses** :
- **Orchestrateurs & harnesses** : Outils pour gérer des workflows multi-agents.
- **Infrastructure & outils** : Solutions pour déployer et superviser des agents.
- **Benchmarks & évaluation** : Méthodes pour tester les performances des agents.
- **Coût & modèles** : Analyse des coûts associés (abonnements, inférence locale, etc.).
- **Sécurité & gouvernance** : Bonnes pratiques pour un usage sécurisé.
- **Glossaire** : Définitions des termes techniques (ex. : RLM, MCP, LSP).
---
#### **Points clés**
- **Diversité des agents** : Le document couvre des agents **génériques** (ex. : Claude Code, OpenCode) et **spécialisés** (ex. : SWE-agent pour résoudre des issues GitHub, Plandex pour le "plan-first").
- **Statuts variés** : Certains projets sont **actifs** (ex. : Prime Agent, OpenCode), tandis que d’autres sont **fermés** (ex. : Claw Code, transformé en musée) ou **stagnants** (ex. : Plandex, Groq Code CLI).
- **Installation et utilisation** : Chaque agent est accompagné de **commandes précises** pour son installation (ex. : `npm install -g @anthropic-ai/claude-code` pour Claude Code) et son utilisation (ex. : `claude "explique ce repo"`).
- **Approches techniques** :
- **Local-first** : Agents conçus pour fonctionner **hors ligne** (ex. : Atomic Agent, NullClaw).
- **Multi-modèles** : Compatibilité avec plusieurs fournisseurs de LLM (ex. : Hermes Agent supporte 300+ providers).
- **Sandboxing** : Solutions pour exécuter du code en toute sécurité (ex. : NemoClaw avec des politiques réseau strictes).
- **Comparatifs** : Le tableau récapitulatif permet de **filtrer rapidement** les agents en fonction de critères comme la compatibilité locale ou le nombre d’étoiles GitHub.
---
#### **Conclusions**
Ce document est une **mine d’informations** pour :
- **Choisir un agent** en fonction de ses besoins (ex. : développement local, collaboration en équipe, résolution de bugs).
- **Comprendre les tendances** du marché (ex. : montée en puissance des agents **local-first**, adoption croissante des modèles **open-source**).
- **Installer et configurer** un agent rapidement grâce aux **instructions pas-à-pas**.
- **Comparer les solutions** via des critères objectifs (statut, stars GitHub, compatibilité locale).
Il s’adresse aussi bien aux **débutants** qu’aux **experts**, avec des sections adaptées à chaque niveau de connaissance. Les **sources citées** (GitHub, arXiv, blogs) garantissent la **fiabilité** des informations.
+26
View File
@@ -0,0 +1,26 @@
# Beloeil et ses Villains
Plongez dans un univers où les légendes locales sortent de l’ombre pour hanter les vivants. Entre les ruelles pavées, usées par des siècles de secrets, et les forêts mystérieuses, dont les arbres semblent murmurer des avertissements aux voyageurs égarés, **Beloeil** est une ville où chaque pierre, chaque souffle de vent, porte le poids d’un passé maudit.
---
### Les Gardiens de l’Obscurité
Les méchants de Beloeil ne sont pas de simples antagonistes. Ils sont les **gardiens d’un héritage sombre**, tissé de magie ancienne, de rivalités familiales et de serments brisés. Leur présence imprègne la ville d’une atmosphère où la frontière entre le bien et le mal s’estompe, où chaque choix semble mener à une nouvelle malédiction.
- **Le Comte Déchu** : Son armure rouillée grince encore entre les murs de son château en ruines. Ses anciens sujets, transformés en spectres, murmurent des prières pour une rédemption qui ne viendra jamais. Son règne, autrefis glorieux, n’est plus qu’un écho de trahisons et de regrets.
- **La Sorcière des Marais** : Ses potions ne se contentent pas d’empoisonner les corps, elles **corrompent les âmes**. Les villageois qui osent s’aventurer près de son repaire reviennent transformés, assoiffés de vengeance, hantés par des voix qu’ils ne reconnaissent plus.
- **Les Frères Jumeaux** : Autrefois unis par un pacte de sang, l’un a trahi l’autre par ambition. Leur malédiction les lie désormais dans une **danse macabre**, condamnés à se combattre pour l’éternité, leurs lames s’entrechoquant dans un ballet sans fin.
---
### Une Ville de Secrets et de Retournements
Beloeil est un lieu où **les apparences trompent**. Le héros tant admiré pourrait cacher un passé taché de crimes. La malédiction qui pèse sur la ville pourrait trouver son origine dans un **amour interdit** entre une fée et un mortel. Chaque recoin cache une intrigue, chaque murmure un avertissement.
Le surnaturel y est omniprésent : sorts oubliés, artefacts maudits, créatures légendaires. **La magie et l’horreur s’y mêlent**, plongeant les lecteurs dans un monde où chaque page révèle un nouveau mystère, une nouvelle terreur.
---
### Une Histoire à Écrire
Beloeil est une toile de fond parfaite pour une narration **riche en intrigues complexes et en retournements inattendus**. Que ce soit à travers des quêtes désespérées, des alliances fragiles ou des trahisons imprévisibles, cette ville offre un terrain fertile pour explorer **l’obscurité de l’âme humaine et la puissance des légendes**.
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
# Nouveau fichier dans Dir
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# PostgreSQL backup script for HabitForge
set -e
# Configuration
BACKUP_DIR="/backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="${BACKUP_DIR}/habitforge_backup_${TIMESTAMP}.sql.gz"
RETENTION_DAYS=7
# Database settings (from environment)
DB_HOST="${POSTGRES_HOST:-db}"
DB_PORT="${POSTGRES_PORT:-5432}"
DB_NAME="${POSTGRES_DB:-habitforge}"
DB_USER="${POSTGRES_USER:-habitforge}"
# Create backup directory if it doesn't exist
mkdir -p "${BACKUP_DIR}"
echo "Starting backup at $(date)"
# Perform backup
PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \
-h "${DB_HOST}" \
-p "${DB_PORT}" \
-U "${DB_USER}" \
-d "${DB_NAME}" \
--format=plain \
--no-owner \
--no-acl | gzip > "${BACKUP_FILE}"
# Check if backup was successful
if [ $? -eq 0 ]; then
echo "Backup completed successfully: ${BACKUP_FILE}"
# Get file size
SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
echo "Backup size: ${SIZE}"
# Remove old backups (older than RETENTION_DAYS)
echo "Removing backups older than ${RETENTION_DAYS} days..."
find "${BACKUP_DIR}" -name "habitforge_backup_*.sql.gz" -type f -mtime +${RETENTION_DAYS} -delete
# List remaining backups
echo "Current backups:"
ls -lh "${BACKUP_DIR}"/habitforge_backup_*.sql.gz 2>/dev/null || echo "No backups found"
else
echo "Backup failed!"
exit 1
fi
echo "Backup finished at $(date)"
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""
Script to import Zepp health data from export-zepp directory into HabitForge database.
Usage: python scripts/import_zepp_data.py [--user-id USER_ID]
"""
import sys
import os
import argparse
# Add parent directory to path to import backend modules
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from backend.integrations.import_zepp import main as import_main, USER_ID as DEFAULT_USER_ID
from backend.integrations import import_zepp
def run_import(user_id: int = None):
"""Run the Zepp data import."""
if user_id:
# Override the default user ID
import_zepp.USER_ID = user_id
print(f"Importing data for user ID: {user_id}")
else:
print(f"Importing data for default user ID: {DEFAULT_USER_ID}")
import_main()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Import Zepp health data into HabitForge")
parser.add_argument(
"--user-id",
type=int,
help="User ID to import data for (default: 1)",
default=None
)
args = parser.parse_args()
run_import(args.user_id)
+162
View File
@@ -0,0 +1,162 @@
"""
Migration script from SQLite to PostgreSQL.
This script migrates data from the existing SQLite database to PostgreSQL.
Usage:
python scripts/migrate_to_postgres.py
Prerequisites:
- PostgreSQL database must be running
- DATABASE_URL environment variable must point to PostgreSQL
- SQLite database must exist at data/habitforge.db
"""
import os
import sys
from datetime import datetime
# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from backend import models
from backend.database import Base, SessionLocal as SQLiteSessionLocal
# PostgreSQL connection
POSTGRES_URL = os.getenv("DATABASE_URL", "postgresql://habitforge:habitforge@localhost:5432/habitforge")
def migrate_data():
"""Migrate data from SQLite to PostgreSQL."""
print("Starting migration from SQLite to PostgreSQL...")
# Create PostgreSQL engine
postgres_engine = create_engine(POSTGRES_URL)
PostgresSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=postgres_engine)
# Create tables in PostgreSQL
print("Creating tables in PostgreSQL...")
Base.metadata.create_all(bind=postgres_engine)
# Get SQLite session
sqlite_session = SQLiteSessionLocal()
# Get PostgreSQL session
postgres_session = PostgresSessionLocal()
try:
# Migrate Users
print("Migrating Users...")
users = sqlite_session.query(models.User).all()
for user in users:
new_user = models.User(
id=user.id,
username=user.username,
hashed_password=user.hashed_password
)
postgres_session.add(new_user)
postgres_session.commit()
print(f"Migrated {len(users)} users")
# Migrate Challenges
print("Migrating Challenges...")
challenges = sqlite_session.query(models.Challenge).all()
for challenge in challenges:
new_challenge = models.Challenge(
id=challenge.id,
name=challenge.name,
period_type=challenge.period_type,
daily_target=challenge.daily_target,
start_date=challenge.start_date,
end_date=challenge.end_date,
display_order=challenge.display_order,
user_id=challenge.user_id,
icon=challenge.icon,
image_url=challenge.image_url,
description=challenge.description,
unit_type=challenge.unit_type,
rest_days=challenge.rest_days,
frequency=challenge.frequency
)
postgres_session.add(new_challenge)
postgres_session.commit()
print(f"Migrated {len(challenges)} challenges")
# Migrate Tracking
print("Migrating Tracking...")
trackings = sqlite_session.query(models.Tracking).all()
for tracking in trackings:
new_tracking = models.Tracking(
id=tracking.id,
challenge_id=tracking.challenge_id,
date=tracking.date,
reps=tracking.reps,
completed=tracking.completed,
notes=tracking.notes
)
postgres_session.add(new_tracking)
postgres_session.commit()
print(f"Migrated {len(trackings)} trackings")
# Migrate DailyHealthMetrics
print("Migrating DailyHealthMetrics...")
metrics = sqlite_session.query(models.DailyHealthMetrics).all()
for metric in metrics:
new_metric = models.DailyHealthMetrics(
id=metric.id,
user_id=metric.user_id,
date=metric.date,
step_count=metric.step_count,
calories_burned=metric.calories_burned,
distance_meters=metric.distance_meters,
sleep_duration_minutes=metric.sleep_duration_minutes,
deep_sleep_minutes=metric.deep_sleep_minutes,
light_sleep_minutes=metric.light_sleep_minutes,
rem_sleep_minutes=metric.rem_sleep_minutes,
awake_duration_minutes=metric.awake_duration_minutes,
avg_heart_rate=metric.avg_heart_rate,
min_heart_rate=metric.min_heart_rate,
max_heart_rate=metric.max_heart_rate,
avg_spo2=metric.avg_spo2,
pai_score=metric.pai_score,
weight=metric.weight,
resting_heart_rate=metric.resting_heart_rate,
hrv=metric.hrv
)
postgres_session.add(new_metric)
postgres_session.commit()
print(f"Migrated {len(metrics)} daily health metrics")
print("\nMigration completed successfully!")
print(f"Total records migrated:")
print(f" - Users: {len(users)}")
print(f" - Challenges: {len(challenges)}")
print(f" - Trackings: {len(trackings)}")
print(f" - DailyHealthMetrics: {len(metrics)}")
except Exception as e:
print(f"\nError during migration: {e}")
postgres_session.rollback()
raise
finally:
sqlite_session.close()
postgres_session.close()
postgres_engine.dispose()
if __name__ == "__main__":
# Check if SQLite database exists
if not os.path.exists("data/habitforge.db"):
print("Error: SQLite database not found at data/habitforge.db")
sys.exit(1)
# Confirm migration
print("This will migrate data from SQLite to PostgreSQL.")
print(f"PostgreSQL URL: {POSTGRES_URL}")
response = input("Do you want to continue? (yes/no): ")
if response.lower() != "yes":
print("Migration cancelled.")
sys.exit(0)
migrate_data()
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# Production startup script for HabitForge
set -e
echo "Starting HabitForge in production mode..."
# Wait for database to be ready
echo "Waiting for database..."
while ! pg_isready -h ${POSTGRES_HOST:-db} -U ${POSTGRES_USER:-habitforge} -d ${POSTGRES_DB:-habitforge} > /dev/null 2>&1; do
echo "Database is unavailable - sleeping"
sleep 1
done
echo "Database is ready!"
# Run migrations
echo "Running database migrations..."
alembic upgrade head
# Start Gunicorn
echo "Starting Gunicorn..."
exec gunicorn backend.main:app \
--workers ${GUNICORN_WORKERS:-4} \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--access-logfile - \
--error-logfile - \
--log-level ${LOG_LEVEL:-info}
+281
View File
@@ -0,0 +1,281 @@
{
"type": "excalidraw",
"version": 2,
"elements": [
{
"id": "E_M7axF81tmVRs3_iSNTB",
"type": "rectangle",
"x": 474,
"y": 146.33334350585938,
"width": 415.3333740234375,
"height": 216.66665649414062,
"angle": 0,
"strokeColor": "#e03131",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": {
"type": 3
},
"seed": 975148614,
"version": 75,
"versionNonce": 1779287411,
"isDeleted": false,
"boundElements": [
{
"id": "9M-JFwSrjH6KSHIaBvvTI",
"type": "arrow"
}
],
"updated": 1789701383205,
"created": 1789695569357,
"link": null,
"locked": false
},
{
"id": "r0qk76e8Zwvzg03cCJTet",
"type": "diamond",
"x": 602,
"y": 154.33334350585938,
"width": 195.3333740234375,
"height": 202,
"angle": 0,
"strokeColor": "#f08c00",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": {
"type": 2
},
"seed": 1824592326,
"version": 88,
"versionNonce": 1410387869,
"isDeleted": false,
"boundElements": [],
"updated": 1789701386097,
"created": 1789695572009,
"link": null,
"locked": false
},
{
"id": "9KK6ZVdnduPeYFFFCFAsy",
"type": "ellipse",
"x": 980.6668701171875,
"y": 170.0000457763672,
"width": 162.66668701171875,
"height": 141.3333740234375,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": {
"type": 2
},
"seed": 1669693053,
"version": 190,
"versionNonce": 1391288573,
"isDeleted": false,
"boundElements": [
{
"id": "9M-JFwSrjH6KSHIaBvvTI",
"type": "arrow"
}
],
"updated": 1789701376494,
"link": null,
"locked": false
},
{
"id": "9M-JFwSrjH6KSHIaBvvTI",
"type": "arrow",
"x": 895.3334350585938,
"y": 248.66673278808594,
"width": 82.66668701171875,
"height": 3.333343505859375,
"angle": 0,
"strokeColor": "#e03131",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": {
"type": 2
},
"seed": 1977221331,
"version": 35,
"versionNonce": 23426611,
"isDeleted": false,
"boundElements": null,
"updated": 1789701380015,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
82.66668701171875,
-3.333343505859375
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "E_M7axF81tmVRs3_iSNTB",
"focus": 0.022412363413033574,
"gap": 6.00006103515625
},
"endBinding": {
"elementId": "9KK6ZVdnduPeYFFFCFAsy",
"focus": -0.017303733989364446,
"gap": 2.836434396181596
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
}
],
"appState": {
"showWelcomeScreen": false,
"theme": "dark",
"collaborators": {},
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "solid",
"currentItemFontFamily": 5,
"currentItemFontSize": 20,
"currentItemOpacity": 100,
"currentItemRoughness": 1,
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#f08c00",
"currentItemRoundness": "round",
"currentItemArrowType": "round",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 2,
"currentItemTextAlign": "left",
"currentHoveredFontFamily": null,
"cursorButton": "up",
"activeEmbeddable": null,
"newElement": null,
"editingTextElement": null,
"editingGroupId": null,
"editingLinearElement": null,
"activeTool": {
"type": "selection",
"customType": null,
"locked": false,
"lastActiveTool": {
"type": "selection",
"customType": null,
"locked": false,
"lastActiveTool": null
}
},
"penMode": false,
"penDetected": false,
"errorMessage": null,
"exportBackground": true,
"exportScale": 1,
"exportEmbedScene": false,
"exportWithDarkMode": false,
"fileHandle": null,
"gridSize": 20,
"gridStep": 5,
"gridModeEnabled": false,
"isBindingEnabled": true,
"defaultSidebarDockedPreference": false,
"isLoading": false,
"isResizing": false,
"isRotating": false,
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-2026-09-17-2148",
"contextMenu": null,
"openMenu": null,
"openPopup": null,
"openSidebar": null,
"openDialog": null,
"pasteDialog": {
"shown": false,
"data": null
},
"previousSelectedElementIds": {
"E_M7axF81tmVRs3_iSNTB": true
},
"resizingElement": null,
"scrolledOutside": false,
"scrollX": -23.33343505859375,
"scrollY": 101.99995422363281,
"selectedElementIds": {
"r0qk76e8Zwvzg03cCJTet": true
},
"hoveredElementIds": {},
"selectedGroupIds": {},
"selectedElementsAreBeingDragged": false,
"selectionElement": null,
"shouldCacheIgnoreZoom": false,
"stats": {
"open": false,
"panels": 3
},
"startBoundElement": null,
"suggestedBindings": [],
"frameRendering": {
"enabled": true,
"clip": true,
"name": true,
"outline": true
},
"frameToHighlight": null,
"editingFrame": null,
"elementsToHighlight": null,
"toast": null,
"viewBackgroundColor": "#ffffff",
"zenModeEnabled": false,
"zoom": {
"value": 1
},
"viewModeEnabled": false,
"pendingImageElementId": null,
"showHyperlinkPopup": false,
"selectedLinearElement": null,
"snapLines": [],
"originSnapOffset": null,
"objectsSnapModeEnabled": false,
"userToFollow": null,
"followedBy": {},
"isCropping": false,
"croppingElementId": null,
"searchMatches": [],
"offsetLeft": 0,
"offsetTop": 0,
"width": 1280,
"height": 800
},
"files": {}
}