AI студия Владимира Ломтева
УСЛУГИПРОЕКТЫСТАТЬИБАЗА ЗНАНИЙМаркетплейсПолезные сервисы

Оставьте заявку,
чтобы обсудить проект

Напишите ваш вопрос, не забудьте указать телефон. Мы перезвоним и все расскажем.

Контакты

Москва

Работаем по всей России
и миру (онлайн)

+7 (999) 760-24-41

Ежедневно с 9:00 до 21:00

lamooof@gmail.com

По вопросам сотрудничества

TelegramWhatsApp

Есть предложение?

Напишите нам в мессенджеры

© 2025 AI студия Владимира Ломтева

Политика конфиденциальностиСогласие на обработку ПДн|ИНН 623412173261
    Appwrite Typescript — Скилл для ИИ-агентов | AI Рассвет

    Appwrite Typescript

    Appwrite TypeScript SDK skill. Use when building browser based JavaScript/TypeScript apps, React Native mobile apps, or server side Node.js/Deno backends with Appwrite. Covers client side auth (email, OAuth, anonymous), database queries, file uploads, real time subscriptions, and server side admin via API keys for user management, database administration, storage, and functions.

    Скиллы для разработки#GitHub#appwrite/skills#skills.sh
    Скачивания
    0
    В избранном
    0
    Комментарии
    0
    Просмотры
    2

    Установить скилл

    Добавьте инструмент одной командой или скачайте проверенный архив версии.

    npx skills add appwrite/skills --skill appwrite-typescript
    Скачать ZIP
    Версия
    1.0.0+a062e60a3e4d
    Автор
    Владимир Ломтев
    Репозиторий
    appwrite/skills
    GitHub: appwrite/skills

    Как установить

    1. 1Скопируйте команду из блока установки.
    2. 2Запустите её в терминале из каталога проекта.

    Документация

    Appwrite TypeScript SDK

    Installation

    ## Web
    npm install appwrite
    
    ## React Native
    npm install react-native-appwrite
    
    ## Node.js / Deno
    npm install node-appwrite
    

    Setting Up the Client

    Client-side (Web / React Native)

    // Web
    import { Client, Account, TablesDB, Storage, ID, Query } from 'appwrite';
    
    // React Native
    import { Client, Account, TablesDB, Storage, ID, Query } from 'react-native-appwrite';
    
    const client = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]');
    

    Server-side (Node.js / Deno)

    import { Client, Users, TablesDB, Storage, Functions, ID, Query } from 'node-appwrite';
    
    const client = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject(process.env.APPWRITE_PROJECT_ID)
        .setKey(process.env.APPWRITE_API_KEY);
    

    Code Examples

    Authentication (client-side)

    const account = new Account(client);
    
    // Email signup
    await account.create({
        userId: ID.unique(),
        email: 'user@example.com',
        password: 'password123',
        name: 'User Name'
    });
    
    // Email login
    const session = await account.createEmailPasswordSession({
        email: 'user@example.com',
        password: 'password123'
    });
    
    // OAuth login (Web)
    account.createOAuth2Session({
        provider: OAuthProvider.Github,
        success: 'https://example.com/success',
        failure: 'https://example.com/fail',
        scopes: ['repo', 'user'] // optional — provider-specific scopes
    });
    
    // Get current user
    const user = await account.get();
    
    // Logout
    await account.deleteSession({ sessionId: 'current' });
    

    OAuth 2 Login (React Native)

    Important: createOAuth2Session() does not work on React Native. You must use createOAuth2Token() with deep linking instead.

    Setup

    Install the required dependencies:

    npx expo install react-native-appwrite react-native-url-polyfill
    npm install expo-auth-session expo-web-browser expo-linking
    

    Set the URL scheme in your app.json:

    {
      "expo": {
        "scheme": "appwrite-callback-[PROJECT_ID]"
      }
    }
    

    OAuth Flow

    import { Client, Account, OAuthProvider } from 'react-native-appwrite';
    import { makeRedirectUri } from 'expo-auth-session';
    import * as WebBrowser from 'expo-web-browser';
    
    const client = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]');
    
    const account = new Account(client);
    
    async function oauthLogin(provider: OAuthProvider) {
        // Create deep link that works across Expo environments
        const deepLink = new URL(makeRedirectUri({ preferLocalhost: true }));
        const scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'appwrite-callback-[PROJECT_ID]://'
    
        // Get the OAuth login URL
        const loginUrl = await account.createOAuth2Token({
            provider,
            success: `${deepLink}`,
            failure: `${deepLink}`,
        });
    
        // Open browser and listen for the scheme redirect
        const result = await WebBrowser.openAuthSessionAsync(`${loginUrl}`, scheme);
    
        if (result.type !== 'success') return;
    
        // Extract credentials from the redirect URL
        const url = new URL(result.url);
        const secret = url.searchParams.get('secret');
        const userId = url.searchParams.get('userId');
    
        // Create session with the OAuth credentials
        await account.createSession({ userId, secret });
    }
    
    // Usage
    await oauthLogin(OAuthProvider.Github);
    await oauthLogin(OAuthProvider.Google);
    

    User Management (server-side)

    const users = new Users(client);
    
    // Create user
    const user = await users.create({
        userId: ID.unique(),
        email: 'user@example.com',
        password: 'password123',
        name: 'User Name'
    });
    
    // List users
    const list = await users.list({ queries: [Query.limit(25)] });
    
    // Get user
    const fetched = await users.get({ userId: '[USER_ID]' });
    
    // Delete user
    await users.delete({ userId: '[USER_ID]' });
    

    Database Operations

    Note: Use TablesDB (not the deprecated Databases class) for all new code. Only use Databases if the existing codebase already relies on it or the user explicitly requests it.

    Tip: Prefer the object-params calling style (e.g., { databaseId: '...' }) for all SDK method calls. Only use positional arguments if the existing codebase already uses them or the user explicitly requests it.

    const tablesDB = new TablesDB(client);
    
    // Create database (server-side only)
    const db = await tablesDB.create({ databaseId: ID.unique(), name: 'My Database' });
    
    // Create table (server-side only)
    const col = await tablesDB.createTable({
        databaseId: '[DATABASE_ID]',
        tableId: ID.unique(),
        name: 'My Table'
    });
    
    // Create row
    const doc = await tablesDB.createRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: ID.unique(),
        data: { title: 'Hello World', content: 'Example content' }
    });
    
    // List rows with query
    const results = await tablesDB.listRows({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        queries: [Query.equal('status', 'active'), Query.limit(10)]
    });
    
    // Get row
    const row = await tablesDB.getRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: '[ROW_ID]'
    });
    
    // Update row
    await tablesDB.updateRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: '[ROW_ID]',
        data: { title: 'Updated Title' }
    });
    
    // Delete row
    await tablesDB.deleteRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: '[ROW_ID]'
    });
    

    String Column Types

    Note: The legacy string type is deprecated. Use explicit column types for all new columns.

    Type Max characters Indexing Storage
    varchar 16,383 Full index (if size ≤ 768) Inline in row
    text 16,383 Prefix only Off-page
    mediumtext 4,194,303 Prefix only Off-page
    longtext 1,073,741,823 Prefix only Off-page
    • varchar is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.
    • text, mediumtext, and longtext are stored off-page (only a 20-byte pointer lives in the row), so they don't consume the row size budget. size is not required for these types.
    // Create table with explicit string column types
    await tablesDB.createTable({
        databaseId: '[DATABASE_ID]',
        tableId: ID.unique(),
        name: 'articles',
        columns: [
            { key: 'title',    type: 'varchar',    size: 255, required: true  },  // inline, fully indexable
            { key: 'summary',  type: 'text',                  required: false },  // off-page, prefix index only
            { key: 'body',     type: 'mediumtext',            required: false },  // up to ~4 M chars
            { key: 'raw_data', type: 'longtext',              required: false },  // up to ~1 B chars
        ]
    });
    

    TypeScript Generics

    import { Models } from 'appwrite';
    // Server-side: import from 'node-appwrite'
    
    // Define a typed interface for your row data
    interface Todo {
        title: string;
        done: boolean;
        priority: number;
    }
    
    // listRows returns Models.DocumentList<Models.Document> by default
    // Cast or use generics for typed results
    const results = await tablesDB.listRows({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        queries: [Query.equal('done', false)]
    });
    
    // Each document includes built-in fields alongside your data
    const doc = results.documents[0];
    doc.$id;            // string — unique row ID
    doc.$createdAt;     // string — ISO 8601 creation timestamp
    doc.$updatedAt;     // string — ISO 8601 update timestamp
    doc.$permissions;   // string[] — permission strings
    doc.$databaseId;    // string
    doc.$collectionId;  // string
    
    // Common model types
    // Models.User<Preferences>  — user account
    // Models.Session             — auth session
    // Models.File                — storage file metadata
    // Models.Team                — team object
    // Models.Execution           — function execution result
    // Models.DocumentList<T>     — paginated list with total count
    

    Query Methods

    // Filtering
    Query.equal('field', 'value')           // field == value (or pass array for IN)
    Query.notEqual('field', 'value')        // field != value
    Query.lessThan('field', 100)            // field < value
    Query.lessThanEqual('field', 100)       // field <= value
    Query.greaterThan('field', 100)         // field > value
    Query.greaterThanEqual('field', 100)    // field >= value
    Query.between('field', 1, 100)          // 1 <= field <= 100
    Query.isNull('field')                   // field is null
    Query.isNotNull('field')                // field is not null
    Query.startsWith('field', 'prefix')     // string starts with prefix
    Query.endsWith('field', 'suffix')       // string ends with suffix
    Query.contains('field', 'substring')    // string/array contains value
    Query.search('field', 'keywords')       // full-text search (requires full-text index)
    
    // Sorting
    Query.orderAsc('field')                 // sort ascending
    Query.orderDesc('field')                // sort descending
    
    // Pagination
    Query.limit(25)                         // max rows returned (default 25, max 100)
    Query.offset(0)                         // skip N rows
    Query.cursorAfter('[ROW_ID]')           // paginate after this row ID (preferred for large datasets)
    Query.cursorBefore('[ROW_ID]')          // paginate before this row ID
    
    // Selection
    Query.select(['field1', 'field2'])      // return only specified fields
    
    // Logical
    Query.or([Query.equal('a', 1), Query.equal('b', 2)])   // OR condition
    Query.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)])  // explicit AND (queries are AND by default)
    

    File Storage

    const storage = new Storage(client);
    
    // Upload file (client-side — from file input)
    const file = await storage.createFile({
        bucketId: '[BUCKET_ID]',
        fileId: ID.unique(),
        file: document.getElementById('file-input').files[0]
    });
    
    // Upload file (server-side — from path)
    import { InputFile } from 'node-appwrite/file';
    
    const file2 = await storage.createFile({
        bucketId: '[BUCKET_ID]',
        fileId: ID.unique(),
        file: InputFile.fromPath('/path/to/file.png', 'file.png')
    });
    
    // List files
    const files = await storage.listFiles({ bucketId: '[BUCKET_ID]' });
    
    // Get file preview (image)
    const preview = storage.getFilePreview({
        bucketId: '[BUCKET_ID]',
        fileId: '[FILE_ID]',
        width: 300,
        height: 300
    });
    
    // Download file
    const download = await storage.getFileDownload({
        bucketId: '[BUCKET_ID]',
        fileId: '[FILE_ID]'
    });
    
    // Delete file
    await storage.deleteFile({ bucketId: '[BUCKET_ID]', fileId: '[FILE_ID]' });
    

    InputFile Factory Methods (server-side)

    import { InputFile } from 'node-appwrite/file';
    
    InputFile.fromPath('/path/to/file.png', 'file.png')          // from filesystem path
    InputFile.fromBuffer(buffer, 'file.png')                       // from Buffer
    InputFile.fromStream(readableStream, 'file.png', size)         // from ReadableStream (size in bytes required)
    InputFile.fromPlainText('Hello world', 'hello.txt')            // from string content
    

    Teams

    const teams = new Teams(client);
    
    // Create team
    const team = await teams.create({ teamId: ID.unique(), name: 'Engineering' });
    
    // List teams
    const list = await teams.list();
    
    // Create membership (invite a user by email)
    const membership = await teams.createMembership({
        teamId: '[TEAM_ID]',
        roles: ['editor'],
        email: 'user@example.com',
    });
    
    // List memberships
    const members = await teams.listMemberships({ teamId: '[TEAM_ID]' });
    
    // Update membership roles
    await teams.updateMembership({
        teamId: '[TEAM_ID]',
        membershipId: '[MEMBERSHIP_ID]',
        roles: ['admin'],
    });
    
    // Delete team
    await teams.delete({ teamId: '[TEAM_ID]' });
    

    Role-based access: Use Role.team('[TEAM_ID]') for all team members or Role.team('[TEAM_ID]', 'editor') for a specific team role when setting permissions.

    Real-time Subscriptions (client-side)

    import { Realtime, Channel } from 'appwrite';
    
    const realtime = new Realtime(client);
    
    // Subscribe to row changes
    const subscription = await realtime.subscribe(
        Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),
        (response) => {
            console.log(response.events);   // e.g. ['tablesdb.*.tables.*.rows.*.create']
            console.log(response.payload);  // the affected resource
        }
    );
    
    // Subscribe to a specific row
    await realtime.subscribe(
        Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row('[ROW_ID]'),
        (response) => { /* ... */ }
    );
    
    // Subscribe to multiple channels
    await realtime.subscribe([
        Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),
        Channel.bucket('[BUCKET_ID]').file(),
    ], (response) => { /* ... */ });
    
    // Unsubscribe
    await subscription.close();
    

    Available channels:

    Channel Description
    account Changes to the authenticated user's account
    tablesdb.[DB_ID].tables.[TABLE_ID].rows All rows in a table
    tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID] A specific row
    buckets.[BUCKET_ID].files All files in a bucket
    buckets.[BUCKET_ID].files.[FILE_ID] A specific file
    teams Changes to teams the user belongs to
    teams.[TEAM_ID] Changes to a specific team
    memberships Changes to the user's team memberships
    memberships.[MEMBERSHIP_ID] A specific membership
    functions.[FUNCTION_ID].executions Execution updates for a function

    The response object includes: events (array of event strings), payload (the affected resource), channels (channels matched), and timestamp (ISO 8601).

    Serverless Functions (server-side)

    const functions = new Functions(client);
    
    // Execute function
    const execution = await functions.createExecution({
        functionId: '[FUNCTION_ID]',
        body: JSON.stringify({ key: 'value' })
    });
    
    // List executions
    const executions = await functions.listExecutions({ functionId: '[FUNCTION_ID]' });
    

    Writing a Function Handler (Node.js runtime)

    When deploying your own Appwrite Function, the entry point file must export a default async function:

    // src/main.js (or src/main.ts)
    export default async ({ req, res, log, error }) => {
        // Request properties
        // req.body        — raw request body (string)
        // req.bodyJson    — parsed JSON body (object, or undefined if not JSON)
        // req.headers     — request headers (object)
        // req.method      — HTTP method (GET, POST, PUT, DELETE, PATCH)
        // req.path        — URL path (e.g. '/hello')
        // req.query       — parsed query parameters (object)
        // req.queryString — raw query string
    
        log('Processing request: ' + req.method + ' ' + req.path);
    
        if (req.method === 'GET') {
            return res.json({ message: 'Hello from Appwrite Function!' });
        }
    
        const data = req.bodyJson;
        if (!data?.name) {
            error('Missing name field');
            return res.json({ error: 'Name is required' }, 400);
        }
    
        // Response methods
        return res.json({ success: true });                    // JSON (sets Content-Type automatically)
        // return res.text('Hello');                           // plain text
        // return res.empty();                                 // 204 No Content
        // return res.redirect('https://example.com');         // 302 Redirect
        // return res.send('data', 200, { 'X-Custom': '1' }); // custom body, status, headers
    };
    

    Server-Side Rendering (SSR) Authentication

    SSR apps (Next.js, SvelteKit, Nuxt, Remix, Astro) use the server SDK (node-appwrite) to handle auth. You need two clients:

    • Admin client — uses an API key, creates sessions, bypasses rate limits (reusable singleton)
    • Session client — uses a session cookie, acts on behalf of a user (create per-request, never share)
    import { Client, Account, OAuthProvider } from 'node-appwrite';
    
    // Admin client (reusable)
    const adminClient = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]')
        .setKey(process.env.APPWRITE_API_KEY);
    
    // Session client (create per-request)
    const sessionClient = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]');
    
    const session = req.cookies['a_session_[PROJECT_ID]'];
    if (session) {
        sessionClient.setSession(session);
    }
    

    Email/Password Login

    app.post('/login', async (req, res) => {
        const account = new Account(adminClient);
        const session = await account.createEmailPasswordSession({
            email: req.body.email,
            password: req.body.password,
        });
    
        // Cookie name must be a_session_<PROJECT_ID>
        res.cookie('a_session_[PROJECT_ID]', session.secret, {
            httpOnly: true,
            secure: true,
            sameSite: 'strict',
            expires: new Date(session.expire),
            path: '/',
        });
    
        res.json({ success: true });
    });
    

    Authenticated Requests

    app.get('/user', async (req, res) => {
        const session = req.cookies['a_session_[PROJECT_ID]'];
        if (!session) return res.status(401).json({ error: 'Unauthorized' });
    
        // Create a fresh session client per request
        const sessionClient = new Client()
            .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
            .setProject('[PROJECT_ID]')
            .setSession(session);
    
        const account = new Account(sessionClient);
        const user = await account.get();
        res.json(user);
    });
    

    OAuth2 SSR Flow

    // Step 1: Redirect to OAuth provider
    app.get('/oauth', async (req, res) => {
        const account = new Account(adminClient);
        const redirectUrl = await account.createOAuth2Token({
            provider: OAuthProvider.Github,
            success: 'https://example.com/oauth/success',
            failure: 'https://example.com/oauth/failure',
        });
        res.redirect(redirectUrl);
    });
    
    // Step 2: Handle callback — exchange token for session
    app.get('/oauth/success', async (req, res) => {
        const account = new Account(adminClient);
        const session = await account.createSession({
            userId: req.query.userId,
            secret: req.query.secret,
        });
    
        res.cookie('a_session_[PROJECT_ID]', session.secret, {
            httpOnly: true, secure: true, sameSite: 'strict',
            expires: new Date(session.expire), path: '/',
        });
        res.json({ success: true });
    });
    

    Cookie security: Always use httpOnly, secure, and sameSite: 'strict' to prevent XSS. The cookie name must be a_session_<PROJECT_ID>.

    Forwarding user agent: Call sessionClient.setForwardedUserAgent(req.headers['user-agent']) to record the end-user's browser info for debugging and security.

    Error Handling

    import { AppwriteException } from 'appwrite';
    // Server-side: import from 'node-appwrite'
    
    try {
        const doc = await tablesDB.getRow({
            databaseId: '[DATABASE_ID]',
            tableId: '[TABLE_ID]',
            rowId: '[ROW_ID]',
        });
    } catch (err) {
        if (err instanceof AppwriteException) {
            console.log(err.message);   // human-readable error message
            console.log(err.code);      // HTTP status code (number)
            console.log(err.type);      // Appwrite error type string (e.g. 'document_not_found')
            console.log(err.response);  // full response body (object)
        }
    }
    

    Common error codes:

    Code Meaning
    401 Unauthorized — missing or invalid session/API key
    403 Forbidden — insufficient permissions for this action
    404 Not found — resource does not exist
    409 Conflict — duplicate ID or unique constraint violation
    429 Rate limited — too many requests, retry after backoff

    Permissions & Roles (Critical)

    Appwrite uses permission strings to control access to resources. Each permission pairs an action (read, update, delete, create, or write which grants create + update + delete) with a role target. By default, no user has access unless permissions are explicitly set at the row/file level or inherited from the table/bucket settings. Permissions are arrays of strings built with the Permission and Role helpers.

    import { Permission, Role } from 'appwrite';
    // Server-side: import from 'node-appwrite'
    

    Database Row with Permissions

    const doc = await tablesDB.createRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: ID.unique(),
        data: { title: 'Hello World' },
        permissions: [
            Permission.read(Role.user('[USER_ID]')),     // specific user can read
            Permission.update(Role.user('[USER_ID]')),   // specific user can update
            Permission.read(Role.team('[TEAM_ID]')),     // all team members can read
            Permission.read(Role.any()),                 // anyone (including guests) can read
        ]
    });
    

    File Upload with Permissions

    const file = await storage.createFile({
        bucketId: '[BUCKET_ID]',
        fileId: ID.unique(),
        file: document.getElementById('file-input').files[0],
        permissions: [
            Permission.read(Role.any()),
            Permission.update(Role.user('[USER_ID]')),
            Permission.delete(Role.user('[USER_ID]')),
        ]
    });
    

    When to set permissions: Set row/file-level permissions when you need per-resource access control. If all rows in a table share the same rules, configure permissions at the table/bucket level and leave row permissions empty.

    Common mistakes:

    • Forgetting permissions — the resource becomes inaccessible to all users (including the creator)
    • Role.any() with write/update/delete — allows any user, including unauthenticated guests, to modify or remove the resource
    • Permission.read(Role.any()) on sensitive data — makes the resource publicly readable

    Требования и возможности

    Источник пакета
    https://github.com/appwrite/skills/tree/a062e60a3e4d63b29002066c2f4b3e05ef6dcc44/skills/appwrite-typescript

    Файлы версии

    ПутьРазмерSHA256
    SKILL.md22569cbc3f29bfb7a308f...

    Частые вопросы

    Как установить Appwrite Typescript?
    Используйте команду npx skills add appwrite/skills --skill appwrite-typescript или скачайте ZIP-архив.
    Можно ли скачать Appwrite Typescript бесплатно?
    Да, опубликованную версию можно скачать из маркетплейса бесплатно.

    Похожие инструменты

    Смотреть все
    React DoctorUse when finishing a feature, fixing a bug, before committing React code, or when the user types `/doctor`, asks to scan, triage, or clean up React diagnostics. Covers lint, accessibility, bundle size, architecture. Includes a regression check and a full local-triage workflow that fetches the canonical playbook.Argent Android Emulator SetupSet up and connect to an Android emulator using argent MCP tools. Use when starting a new session on Android, booting an emulator, getting a device serial, or before any UI interaction task.Fireworks Tech GraphCreate technical diagrams such as software architecture, data flow, flowcharts, sequence diagrams, C4 reviews, cloud deployments, event streams, observability investigations, agent/memory systems, UML, ER, network topology, timelines, and technical concept maps, then export SVG, PNG, focused semantic SVG-to-GIF motion, or offline interactive HTML. Treat direct requests such as "Generate a GIF", "生成 GIF", or "制作 GIF" as motion requests, and use this skill when the user asks to visualize a system or engineering concept. Do not use for photos, raster artwork, or quantitative data charts.
    Комментарии

    Войдите, чтобы оставить комментарий.

    Комментариев пока нет.

    Установить скилл

    Добавьте инструмент одной командой или скачайте проверенный архив версии.

    npx skills add appwrite/skills --skill appwrite-typescript
    Скачать ZIP
    Версия
    1.0.0+a062e60a3e4d
    Автор
    Владимир Ломтев
    Репозиторий
    appwrite/skills
    GitHub: appwrite/skills
    Modern Web GuidanceSearch tool for modern web development best practices. MANDATORY: Execute FIRST for all HTML/CSS and clientside JS tasks. Do NOT skip — web APIs evolve rapidly and training weights contain obsolete patterns.