{
  "schemaVersion": "1.0",
  "item": {
    "slug": "fastmode",
    "name": "FastMode CMS - Host, Deploy, Manage Websites for Free",
    "source": "tencent",
    "type": "skill",
    "category": "其他",
    "sourceUrl": "https://clawhub.ai/arihgoldstein/fastmode",
    "canonicalUrl": "https://clawhub.ai/arihgoldstein/fastmode",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/fastmode",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=fastmode",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "slug": "fastmode",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-29T05:55:27.603Z",
      "expiresAt": "2026-05-06T05:55:27.603Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=fastmode",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=fastmode",
        "contentDisposition": "attachment; filename=\"fastmode-1.5.3.zip\"",
        "redirectLocation": null,
        "bodySnippet": null,
        "slug": "fastmode"
      },
      "scope": "item",
      "summary": "Item download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this item.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/fastmode"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/fastmode",
    "agentPageUrl": "https://openagent3.xyz/skills/fastmode/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fastmode/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fastmode/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "FastMode CLI — Complete Agent Reference",
        "body": "FastMode lets you create a live website, deploy it to the cloud, and manage all its content — entirely from the command line. One-time browser login for OAuth authentication, then every operation runs in the terminal. No local servers, no manual dashboards.\n\nFree cloud hosting — every site gets a live URL at yoursite.fastmode.ai\nFree SSL — HTTPS included automatically\nCustom domains — connect any domain (e.g. www.example.com)\nFull CMS — any content structure (blog, team, products, portfolios, anything)\nAgent-native — every operation works via CLI, zero human intervention needed"
      },
      {
        "title": "Table of Contents",
        "body": "End-to-End Workflow\nCRITICAL: Before You Build Anything\nWebsite Analysis\nCommand Reference\nProject Resolution\nSchema & Field Types\nContent Items\nClient Portal Management\nPackage Structure\nManifest Format\nTemplate Syntax (includes SEO rules, image handling, forms, inline editing)\nDeployment & Build Status\nValidation\nCommon Mistakes & How to Fix Them\nPre-Deployment Checklist\nError Handling & Exit Codes"
      },
      {
        "title": "End-to-End Workflow",
        "body": "This is the complete sequence to go from nothing to a live website.\n\n# 1. Authenticate (one-time — credentials persist at ~/.fastmode/credentials.json)\nfastmode login\n\n# 2. Create a project (gets a free hosted URL instantly)\nfastmode projects create \"Acme Corp\"\nfastmode use \"Acme Corp\"\n\n# 3. Define content structure (write schema.json, then sync it)\nfastmode schema sync -f schema.json\n\n# 4. Add content\nfastmode items create posts -n \"Welcome\" -d '{\"title\": \"Welcome to Acme\", \"body\": \"<p>We build great things.</p>\"}'\nfastmode items create team -n \"Jane Doe\" -d '{\"role\": \"Founder\", \"bio\": \"<p>Visionary leader.</p>\"}'\n\n# 5. Build HTML templates with {{tokens}}, package into a zip, validate, deploy\nfastmode validate package site.zip\nfastmode deploy site.zip\n# Deploy waits for build to finish and reports success or failure with error details\n\n# 6. If the build failed, check what went wrong\nfastmode status\n# Fix the issue, then re-deploy"
      },
      {
        "title": "CRITICAL: Before You Build Anything",
        "body": "STOP. Before writing ANY HTML, templates, or manifest.json, complete these steps."
      },
      {
        "title": "Step 1: Check for Existing Projects",
        "body": "fastmode projects\n\nThis lists all the user's existing FastMode projects."
      },
      {
        "title": "Step 2: Decide — Existing or New Project",
        "body": "If projects exist: Ask the user: \"Is this website for one of your existing projects, or should I create a new one?\" Let the user choose.\n\nIf NO projects exist: This is a new user — ask: \"What would you like to name your new project?\""
      },
      {
        "title": "Step 3a: For EXISTING Projects",
        "body": "User selects the project from the list\nRun fastmode use \"Project Name\" to set it as default\nRun fastmode schema show to get the current collections and fields\nUse this schema to build templates with the correct field names"
      },
      {
        "title": "Step 3b: For NEW Projects",
        "body": "Ask for the project name if you don't have it\nRun fastmode projects create \"Project Name\"\nRun fastmode use \"Project Name\"\nYou'll create the schema later with fastmode schema sync\nOptionally generate sample content: fastmode generate-samples"
      },
      {
        "title": "Checkpoint — Confirm Before Continuing",
        "body": "RequirementHow to Get ItProject selected/createdfastmode projects / fastmode projects createDefault setfastmode use \"Project Name\"Schema known (existing)fastmode schema show\n\nIf you don't have a project set, DO NOT PROCEED. Go back to Step 1.\n\nWHY THIS MATTERS:\n\nFor existing projects: The schema determines which fields to use in templates — get it wrong and the build fails\nFor new projects: You need the project before you can deploy\nAlways: The user must confirm which project to use — never assume"
      },
      {
        "title": "Website Analysis (Do This Before Writing Code)",
        "body": "Before writing any HTML or templates, analyze the site:\n\nMap ALL URLs — document every page path (/, /about, /blog, /blog/post-slug, etc.)\nCategorize each page — Static (fixed content), List (shows multiple items), or Detail (single item from a collection)\nIdentify collections — repeating content that should be CMS-managed (blog posts, team members, products, testimonials, etc.)\nDocument assets — all CSS, JS, image, and font file locations\nPRESERVE original URLs — if the site uses /resources for articles, keep /resources. Do NOT change it to /blog. Use the manifest's path configuration."
      },
      {
        "title": "Authentication",
        "body": "fastmode login                          # Open browser for OAuth device flow\nfastmode logout                         # Delete ~/.fastmode/credentials.json\nfastmode whoami                         # Show current user email and name\n\nlogin uses OAuth 2.0 device authorization flow: opens a browser window where the user approves access on fastmode.ai, then credentials are saved automatically. The browser is only needed for this one-time login step.\nOAuth scopes: The token grants access to the user's FastMode projects only (project management, schema editing, content CRUD, deployments). No third-party service access is requested.\nCredentials persist at ~/.fastmode/credentials.json with restricted file permissions (0o600 — owner read/write only). Treat this file as a sensitive secret.\nTokens auto-refresh. If a token expires, the next command will refresh it silently using the stored refresh token.\nIf credentials are missing or invalid, most commands will trigger the login flow automatically.\nlogout deletes ~/.fastmode/credentials.json and revokes the stored tokens."
      },
      {
        "title": "Projects",
        "body": "fastmode projects                       # List all projects (default action)\nfastmode projects list                  # Same as above\nfastmode projects create \"Name\"         # Create a new project\nfastmode projects create \"Name\" --subdomain custom-sub  # Custom subdomain\nfastmode projects create \"Name\" --force                  # Skip similar-name check\nfastmode use <project>                  # Set default project for all commands\n\nprojects create checks for existing projects with similar names. Use --force to skip.\nSubdomain auto-generated from name if not provided (lowercase, hyphens, max 30 chars).\nuse stores the default in ~/.fastmode/config.json. Does NOT validate the project exists."
      },
      {
        "title": "Schema",
        "body": "fastmode schema show                    # Show all collections and fields\nfastmode schema show -p \"Project Name\"  # Specify project\nfastmode schema sync -f schema.json     # Create collections and fields from JSON file\nfastmode schema field-types             # List all available field types (no auth needed)\n\nschema show requires authentication and a project.\nschema sync reads a local JSON file and creates/updates the schema. Skips duplicates. Two-phase: creates collections first, then fields (handles relation dependencies).\nschema field-types works without authentication."
      },
      {
        "title": "Content Items",
        "body": "fastmode items list <collection>                          # List all items\nfastmode items list posts --limit 10 --sort publishedAt --order desc\nfastmode items get <collection> <slug>                    # Get single item\nfastmode items create <collection> -n \"Name\" -d '{\"field\": \"value\"}'\nfastmode items create posts -n \"Title\" -f data.json       # Data from file\nfastmode items create posts -n \"Draft Post\" -d '{}' --draft\nfastmode items update <collection> <slug> -d '{\"field\": \"new value\"}'\nfastmode items update posts my-post -n \"New Title\"\nfastmode items update posts my-post --publish             # Publish a draft\nfastmode items update posts my-post --unpublish           # Revert to draft\nfastmode items delete <collection> <slug> --confirm       # REQUIRES --confirm\nfastmode items relations <collection>                     # Show linkable items for relation fields\nfastmode items relations posts --field author             # Options for specific field\n\nSee the Content Items section below for detailed rules on data formats, relation fields, and drafts."
      },
      {
        "title": "Client Portal Management",
        "body": "fastmode clients list                              # List portal clients with access\nfastmode clients invite client@example.com         # Invite with default permissions\nfastmode clients invite client@example.com -n \"Jane\" --permissions cms.read,cms.write\nfastmode clients invitations                       # List pending invitations\nfastmode clients update-permissions <accessId> --permissions cms.read,editor\nfastmode clients revoke <accessId> --confirm       # REQUIRES --confirm\nfastmode clients cancel-invite <invitationId> --confirm  # REQUIRES --confirm\n\nSee the Client Portal Management section below for details on permissions, invite flow, and examples."
      },
      {
        "title": "Deployment & Build Status",
        "body": "fastmode deploy site.zip                # Deploy and wait for build to finish\nfastmode deploy site.zip --force        # Skip GitHub sync check\nfastmode deploy site.zip --no-wait      # Upload only, don't wait for build\nfastmode deploy site.zip --timeout 300000  # Custom timeout in ms (default: 120000)\nfastmode status                         # Check current build/deploy status\nfastmode deploys                        # List deployment history\nfastmode deploys --limit 5             # Limit number of results\n\nSee Deployment & Build Status below for the full deploy lifecycle."
      },
      {
        "title": "Validation",
        "body": "fastmode validate manifest manifest.json\nfastmode validate template index.html -t custom_index\nfastmode validate template post.html -t custom_detail -c posts\nfastmode validate template post.html -t custom_detail -c posts -p \"My Project\"\nfastmode validate template about.html -t static_page\nfastmode validate package site.zip\n\nTemplate types: custom_index (collection listing), custom_detail (single item), static_page (fixed page).\n-c specifies the collection slug (required for custom_index and custom_detail).\n-p validates tokens against the actual project schema (reports missing fields).\nAll validation commands exit with code 1 on errors — safe for CI/CD pipelines."
      },
      {
        "title": "Documentation & Examples",
        "body": "fastmode examples <type>                # Code examples for a specific pattern\nfastmode guide                          # Full website conversion guide\nfastmode guide templates                # Template syntax guide\nfastmode guide common_mistakes          # Common pitfalls to avoid\nfastmode generate-samples               # Generate placeholder content for empty collections\nfastmode generate-samples -c posts team # Specific collections only\n\nAvailable example types: manifest_basic, manifest_custom_paths, blog_index_template, blog_post_template, team_template, downloads_template, form_handling, asset_paths, data_edit_keys, each_loop, conditional_if, nested_fields, featured_posts, parent_context, equality_comparison, comparison_helpers, youtube_embed, nested_collection_loop, loop_variables, common_mistakes.\n\nAvailable guide sections: full, first_steps, analysis, structure, seo, manifest, templates, tokens, forms, assets, checklist, common_mistakes."
      },
      {
        "title": "Project Resolution",
        "body": "Every project-scoped command (schema, items, deploy, status, etc.) needs a project. Resolution order:\n\n-p / --project flag — explicit on the command: -p \"My Project\" or -p abc123-uuid\nFASTMODE_PROJECT environment variable — set in shell: export FASTMODE_PROJECT=\"My Project\"\nDefault project — saved by fastmode use \"My Project\" in ~/.fastmode/config.json\n\nIf none is set, the command prints an error and exits with code 1:\n\nError: No project specified.\nUse -p <id-or-name>, set FASTMODE_PROJECT env var, or run: fastmode use <project>\n\nProject identifiers can be:\n\nUUID — used directly (e.g. 550e8400-e29b-41d4-a716-446655440000)\nProject name — resolved via API (exact match first, then partial match, case-insensitive)"
      },
      {
        "title": "Creating a Schema",
        "body": "Write a schema.json file and sync it:\n\nfastmode schema sync -f schema.json"
      },
      {
        "title": "schema.json Format",
        "body": "{\n  \"collections\": [\n    {\n      \"slug\": \"posts\",\n      \"name\": \"Blog Posts\",\n      \"nameSingular\": \"Blog Post\",\n      \"fields\": [\n        { \"slug\": \"title\", \"name\": \"Title\", \"type\": \"text\", \"isRequired\": true },\n        { \"slug\": \"excerpt\", \"name\": \"Excerpt\", \"type\": \"textarea\" },\n        { \"slug\": \"body\", \"name\": \"Body\", \"type\": \"richText\" },\n        { \"slug\": \"featured-image\", \"name\": \"Featured Image\", \"type\": \"image\" },\n        { \"slug\": \"category\", \"name\": \"Category\", \"type\": \"select\", \"options\": \"News, Tutorial, Update\" },\n        { \"slug\": \"tags\", \"name\": \"Tags\", \"type\": \"multiSelect\", \"options\": \"JavaScript, Python, DevOps, AI\" },\n        { \"slug\": \"featured\", \"name\": \"Featured\", \"type\": \"boolean\" },\n        { \"slug\": \"author\", \"name\": \"Author\", \"type\": \"relation\", \"referenceCollection\": \"team\" }\n      ]\n    },\n    {\n      \"slug\": \"team\",\n      \"name\": \"Team Members\",\n      \"nameSingular\": \"Team Member\",\n      \"fields\": [\n        { \"slug\": \"role\", \"name\": \"Role\", \"type\": \"text\" },\n        { \"slug\": \"bio\", \"name\": \"Bio\", \"type\": \"richText\" },\n        { \"slug\": \"photo\", \"name\": \"Photo\", \"type\": \"image\" },\n        { \"slug\": \"email\", \"name\": \"Email\", \"type\": \"email\" }\n      ]\n    }\n  ]\n}\n\nTo add fields to existing collections, use fieldsToAdd:\n\n{\n  \"fieldsToAdd\": [\n    {\n      \"collectionSlug\": \"posts\",\n      \"fields\": [\n        { \"slug\": \"reading-time\", \"name\": \"Reading Time\", \"type\": \"number\" }\n      ]\n    }\n  ]\n}\n\nYou can combine collections and fieldsToAdd in the same file. Duplicate collections and fields are automatically skipped."
      },
      {
        "title": "Available Field Types",
        "body": "TypeDescriptionTemplate UsageNotestextSingle-line text{{field}}Titles, names, short stringstextareaMulti-line plain text{{field}}Descriptions, excerptsrichTextFormatted HTML content{{{field}}}MUST use triple bracesnumberNumeric value{{field}}Prices, counts, orderbooleanTrue/false toggle{{#if field}}Toggles, flagsdateDate only{{field}}Birth dates, event datesdatetimeDate and time{{field}}TimestampsimageImage file/URL{{field}}Renders as URLfileDownloadable file (max 10MB){{field}}Link as <a href=\"{{field}}\" download>urlWeb link{{field}}External URLsvideoEmbedYouTube/Vimeo/Wistia/Loom{{field}}Embed URLemailEmail with validation{{field}}Validated email addressesselectSingle dropdown{{field}}Requires \"options\": \"A, B, C\"multiSelectMultiple selections{{field}}Requires \"options\": \"A, B, C\"relationLink to another collection{{field.name}}Requires \"referenceCollection\": \"slug\""
      },
      {
        "title": "Relation Fields — CRITICAL",
        "body": "Relation fields link items between collections (e.g. a post has an author from the team collection). When creating or updating items with relation fields:\n\nYou MUST use the item's UUID, not its name or slug\nUse fastmode items relations <collection> to get the available IDs\nExample: fastmode items relations posts --field author shows team member IDs\n\n# First, find the author's item ID\nfastmode items relations posts --field author\n# Output shows: ID: 550e8400-..., Name: Jane Doe, Slug: jane-doe\n\n# Then use that ID when creating a post\nfastmode items create posts -n \"My Post\" -d '{\"title\": \"My Post\", \"author\": \"550e8400-e29b-41d4-a716-446655440000\"}'\n\nWRONG: \"author\": \"Jane Doe\" — this will NOT work.\nCORRECT: \"author\": \"550e8400-e29b-41d4-a716-446655440000\" — use the UUID."
      },
      {
        "title": "Creating Items",
        "body": "fastmode items create <collection> -n \"Item Name\" -d '{\"field\": \"value\"}'\n\nFlagDescription-n, --name <name>Required. Item name/title.-s, --slug <slug>URL slug. Auto-generated from name if omitted.-d, --data <json>Field data as JSON string.-f, --file <path>Read field data from a JSON file (takes precedence over -d).-p, --project <id>Project ID or name.--draftCreate as unpublished draft.\n\nData rules:\n\n-d value must be valid JSON. Keys are field slugs.\n-f reads from a JSON file. If both -f and -d are given, -f wins.\nIf neither -d nor -f is given, the item is created with just the name (no field data).\nRich text fields accept raw HTML: \"body\": \"<h2>Title</h2><p>Content here.</p>\"\nRelation fields require UUIDs (see above).\nWithout --draft, items are published immediately (publishedAt set to now)."
      },
      {
        "title": "Updating Items",
        "body": "fastmode items update <collection> <slug> -d '{\"field\": \"new value\"}'\n\nFlagDescription-n, --name <name>New name/title.-d, --data <json>Updated fields as JSON. Only provided fields change — others are preserved.-f, --file <path>Read updated data from a JSON file.-p, --project <id>Project ID or name.--publishSet publishedAt to now (make item live).--unpublishSet publishedAt to null (revert to draft).\n\nUpdate is a partial merge. Only the fields you provide in -d are changed. All other fields remain as they are."
      },
      {
        "title": "Deleting Items",
        "body": "fastmode items delete <collection> <slug> --confirm\n\nThe --confirm flag is required. Without it, the command refuses to run and exits with code 1. This is a safety measure — deletion is permanent and cannot be undone.\n\nAlways ask the user for confirmation before deleting."
      },
      {
        "title": "Draft / Publish Mechanics",
        "body": "ActionCommandCreate as published (default)fastmode items create posts -n \"Title\" -d '{...}'Create as draftfastmode items create posts -n \"Title\" -d '{...}' --draftPublish a draftfastmode items update posts my-slug --publishUnpublish (revert to draft)fastmode items update posts my-slug --unpublish\n\nDraft items have publishedAt: null and are not visible on the live site.\nPublished items have a publishedAt timestamp and appear on the live site.\nWithout --draft, new items are published immediately."
      },
      {
        "title": "Client Portal Management",
        "body": "The client portal lets you give external clients (your customers, collaborators) limited access to manage content on your FastMode site. Clients get their own login, separate from your admin account, with configurable permissions."
      },
      {
        "title": "How It Works",
        "body": "You invite a client by email — they receive a unique invite link\nClient clicks the link — creates a password and gets portal access\nClient manages content — based on the permissions you assigned\nYou control access — update permissions or revoke access at any time\n\nThe portal is auto-enabled on the project when you send the first invitation. No manual setup needed."
      },
      {
        "title": "Available Permissions",
        "body": "PermissionDescriptioncms.readView collection itemscms.writeCreate, edit, archive, and delete itemseditorAccess the visual editorforms.readView form submissionsdnsManage DNS settingsapiAccess API and integrationsnotificationsManage notification rulesbillingView plans and manage billing\n\nDefault permissions (used when none specified): cms.read, cms.write, editor, forms.read"
      },
      {
        "title": "Inviting Clients",
        "body": "# Invite with default permissions\nfastmode clients invite client@example.com\n\n# Invite with a name\nfastmode clients invite client@example.com -n \"Jane Smith\"\n\n# Invite with specific permissions\nfastmode clients invite client@example.com -n \"Jane Smith\" --permissions cms.read,forms.read\n\n# Invite with all permissions\nfastmode clients invite client@example.com --permissions cms.read,cms.write,editor,forms.read,dns,api,notifications,billing\n\nThe command returns an invite URL — share this with the client. The link expires in 7 days.\n\nImportant:\n\nEach email can only be invited once per project\nIf a client already has access, the invite will fail\nIf a pending invitation already exists for the email, the invite will fail"
      },
      {
        "title": "Listing Clients and Invitations",
        "body": "# See who has portal access\nfastmode clients list\n\n# See pending (unaccepted) invitations\nfastmode clients invitations\n\nclients list shows the access ID for each client — you need this ID to update permissions or revoke access."
      },
      {
        "title": "Updating Permissions",
        "body": "# First, get the access ID from the list\nfastmode clients list\n\n# Update permissions (replaces ALL existing permissions)\nfastmode clients update-permissions <accessId> --permissions cms.read,cms.write,editor\n\nPermissions are replaced entirely — if a client had cms.read,cms.write,editor,forms.read and you set --permissions cms.read, they will ONLY have cms.read."
      },
      {
        "title": "Revoking Access",
        "body": "# Revoke a client's portal access (requires --confirm)\nfastmode clients revoke <accessId> --confirm\n\nThe --confirm flag is required. Without it, the command refuses to run.\n\nAlways ask the user for confirmation before revoking access.\n\nRevoking access is a soft delete — the client's account still exists but they cannot access this project's portal. Their active sessions are terminated immediately."
      },
      {
        "title": "Canceling Invitations",
        "body": "# Cancel a pending invitation (requires --confirm)\nfastmode clients cancel-invite <invitationId> --confirm\n\nThe invitation link will no longer work. Use fastmode clients invitations to get the invitation ID."
      },
      {
        "title": "Typical Workflow",
        "body": "# 1. Invite your client\nfastmode clients invite designer@agency.com -n \"Design Agency\" --permissions cms.read,cms.write,editor\n\n# 2. Share the invite URL from the output with the client\n\n# 3. Later, check who has access\nfastmode clients list\n\n# 4. Restrict a client to read-only\nfastmode clients update-permissions abc12345 --permissions cms.read\n\n# 5. Remove a client who no longer needs access\nfastmode clients revoke abc12345 --confirm"
      },
      {
        "title": "Package Structure",
        "body": "The deployment package is a .zip file with this exact structure:\n\nsite.zip\n├── manifest.json              # REQUIRED — defines pages and CMS templates\n├── pages/                     # Static HTML pages\n│   ├── index.html             # Homepage (REQUIRED — must have path \"/\")\n│   ├── about.html\n│   └── contact.html\n├── templates/                 # CMS-powered templates (if using collections)\n│   ├── posts_index.html       # Blog listing page\n│   ├── posts_detail.html      # Single blog post page\n│   └── team_index.html        # Team listing page\n└── public/                    # ALL static assets (CSS, JS, images, fonts)\n    ├── css/\n    │   └── style.css\n    ├── js/\n    │   └── main.js\n    └── images/\n        ├── logo.png\n        └── favicon.ico"
      },
      {
        "title": "Strict Rules",
        "body": "manifest.json MUST be at the root of the zip.\nStatic pages go in pages/. One HTML file per page.\nCMS templates go in templates/. Convention: {collection}_index.html and {collection}_detail.html.\nALL static assets go in public/. CSS, JavaScript, images, fonts — everything.\nReference assets with /public/ prefix in HTML. Example: <link href=\"/public/css/style.css\" rel=\"stylesheet\">.\nA homepage is required. One page must have \"path\": \"/\" in the manifest."
      },
      {
        "title": "Critical: Asset Paths",
        "body": "WRONG — will 404:\n\n<link href=\"/assets/css/style.css\" rel=\"stylesheet\">\n<link href=\"/css/style.css\" rel=\"stylesheet\">\n<link href=\"../css/style.css\" rel=\"stylesheet\">\n<script src=\"js/main.js\"></script>\n\nCORRECT:\n\n<link href=\"/public/css/style.css\" rel=\"stylesheet\">\n<script src=\"/public/js/main.js\"></script>\n<img src=\"/public/images/logo.png\" alt=\"Logo\">\n\nThis also applies inside CSS files — background images AND fonts:\n\n/* WRONG */\nbackground-image: url('../images/bg.jpg');\nbackground-image: url('images/bg.jpg');\nsrc: url('../fonts/custom.woff2');\n\n/* CORRECT */\nbackground-image: url('/public/images/bg.jpg');\nsrc: url('/public/fonts/custom.woff2');\n\nAsset path conversion table:\n\nOriginal PathConverted Pathcss/style.css/public/css/style.css../css/style.css/public/css/style.css./images/logo.png/public/images/logo.png/images/logo.png/public/images/logo.png../fonts/custom.woff/public/fonts/custom.woff\n\nExternal URLs (Google Fonts, CDNs, etc.) stay unchanged."
      },
      {
        "title": "Manifest Format",
        "body": "The manifest.json file defines the site structure. It uses a FLAT format for CMS templates (not nested)."
      },
      {
        "title": "Basic Example (Static Only)",
        "body": "{\n  \"pages\": [\n    { \"path\": \"/\", \"file\": \"pages/index.html\", \"title\": \"Home\" },\n    { \"path\": \"/about\", \"file\": \"pages/about.html\", \"title\": \"About\" },\n    { \"path\": \"/contact\", \"file\": \"pages/contact.html\", \"title\": \"Contact\" }\n  ]\n}"
      },
      {
        "title": "With CMS Collections",
        "body": "{\n  \"pages\": [\n    { \"path\": \"/\", \"file\": \"pages/index.html\", \"title\": \"Home\" },\n    { \"path\": \"/about\", \"file\": \"pages/about.html\", \"title\": \"About\" }\n  ],\n  \"cmsTemplates\": {\n    \"postsIndex\": \"templates/posts_index.html\",\n    \"postsIndexPath\": \"/blog\",\n    \"postsDetail\": \"templates/posts_detail.html\",\n    \"postsDetailPath\": \"/blog\",\n    \"teamIndex\": \"templates/team_index.html\",\n    \"teamIndexPath\": \"/team\"\n  }\n}"
      },
      {
        "title": "CMS Template Keys — FLAT Format",
        "body": "Each collection needs 2-4 keys in cmsTemplates. The format is {collectionSlug}Index, {collectionSlug}IndexPath, {collectionSlug}Detail, {collectionSlug}DetailPath.\n\nKeyRequiredDescription{slug}IndexYesPath to the collection listing template file{slug}IndexPathYesURL path for the listing page (e.g. /blog){slug}DetailNoPath to the single item template file{slug}DetailPathNoURL path prefix for item pages (e.g. /blog → /blog/item-slug)\n\nExample for a \"services\" collection:\n\n\"cmsTemplates\": {\n  \"servicesIndex\": \"templates/services_index.html\",\n  \"servicesIndexPath\": \"/services\",\n  \"servicesDetail\": \"templates/services_detail.html\",\n  \"servicesDetailPath\": \"/services\"\n}"
      },
      {
        "title": "Common Manifest Mistakes — AI Agents Frequently Get This Wrong",
        "body": "AI agents frequently use a nested object format or \"collections\" key that FastMode does NOT support. Read carefully.\n\nWRONG — using \"collections\" key (MOST COMMON AI MISTAKE):\n\n{\n  \"collections\": {\n    \"posts\": {\n      \"indexPath\": \"/blog\",\n      \"indexFile\": \"collections/posts/index.html\",\n      \"detailPath\": \"/blog/:slug\",\n      \"detailFile\": \"collections/posts/detail.html\"\n    }\n  }\n}\n\nWRONG — nested objects inside cmsTemplates:\n\n\"cmsTemplates\": {\n  \"posts\": {\n    \"indexPath\": \"/blog\",\n    \"detailPath\": \"/blog\"\n  }\n}\n\nWRONG — singular slug names:\n\n\"postIndex\": \"...\"   // Should be \"postsIndex\"\n\"postDetail\": \"...\"  // Should be \"postsDetail\"\n\nCORRECT — flat keys using cmsTemplates, matching the collection slug exactly:\n\n\"cmsTemplates\": {\n  \"postsIndex\": \"templates/posts_index.html\",\n  \"postsIndexPath\": \"/blog\",\n  \"postsDetail\": \"templates/posts_detail.html\",\n  \"postsDetailPath\": \"/blog\"\n}\n\nKey rules:\n\nUse cmsTemplates, NOT collections\nUse FLAT keys: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath\nDo NOT nest objects inside collection names\nUse fastmode validate manifest manifest.json to catch these errors before deploying"
      },
      {
        "title": "Optional: Head/Body Injection",
        "body": "{\n  \"pages\": [...],\n  \"cmsTemplates\": {...},\n  \"defaultHeadHtml\": \"<link rel=\\\"stylesheet\\\" href=\\\"/public/css/global.css\\\">\",\n  \"defaultBodyEndHtml\": \"<script src=\\\"/public/js/analytics.js\\\"></script>\"\n}"
      },
      {
        "title": "Template Syntax",
        "body": "FastMode templates use Handlebars-style tokens. There are three types of templates:\n\nStatic pages (pages/): Fixed HTML with optional data-edit-key attributes for inline CMS editing and optional {{#each}} loops for dynamic content.\nIndex templates (templates/): Collection listing pages. MUST contain at least one {{#each collectionSlug}} loop.\nDetail templates (templates/): Single item pages. MUST contain CMS tokens like {{name}}, {{{body}}}, etc."
      },
      {
        "title": "SEO Tags — Do NOT Include",
        "body": "FastMode automatically manages all SEO meta tags. Including them in your HTML will cause duplicate tags (bad for SEO ranking). Remove ALL of these from your templates:\n\nTagWhy to Remove<title>...</title>Managed via CMS Settings<meta name=\"description\">Managed via CMS Settings<meta name=\"keywords\">Managed via CMS Settings<meta property=\"og:*\">Open Graph auto-generated<meta name=\"twitter:*\">Twitter cards auto-generated<link rel=\"icon\">Favicon managed in settings<link rel=\"shortcut icon\">Favicon managed in settings<link rel=\"apple-touch-icon\">Managed by FastMode<meta name=\"google-site-verification\">Managed in settings\n\nCorrect <head> structure:\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <!-- SEO managed by Fast Mode — do not add title, description, or OG tags -->\n  <link rel=\"stylesheet\" href=\"/public/css/style.css\">\n  <!-- External fonts, scripts, etc. are fine -->\n  <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap\" rel=\"stylesheet\">\n</head>"
      },
      {
        "title": "Built-in Fields (Every Item Has These)",
        "body": "TokenDescriptionExample{{name}}Item name/title<h1>{{name}}</h1>{{slug}}URL slug<a href=\"/posts/{{slug}}\">{{url}}Full URL to detail page<a href=\"{{url}}\">Read more</a>{{publishedAt}}Publish date<time>{{publishedAt}}</time>{{createdAt}}Creation date{{updatedAt}}Last modified date"
      },
      {
        "title": "Regular Fields — Double Braces {{field}}",
        "body": "Used for text, number, date, image, url, email, select, boolean fields:\n\n<h1>{{name}}</h1>\n<p>{{excerpt}}</p>\n<img src=\"{{featured-image}}\" alt=\"{{name}}\">\n<span>Category: {{category}}</span>\n<a href=\"{{website-url}}\">Visit</a>"
      },
      {
        "title": "Rich Text Fields — Triple Braces {{{field}}}",
        "body": "CRITICAL: Rich text fields contain HTML. You MUST use triple braces {{{ }}} so the HTML renders correctly. Double braces will escape the HTML and display raw tags as text.\n\n<!-- CORRECT — HTML renders properly -->\n<div class=\"content\">{{{body}}}</div>\n<div class=\"bio\">{{{bio}}}</div>\n\n<!-- WRONG — HTML appears as escaped text like &lt;p&gt;Hello&lt;/p&gt; -->\n<div class=\"content\">{{body}}</div>"
      },
      {
        "title": "Loops — {{#each collection}}",
        "body": "Used in index templates and static pages to iterate over collection items.\n\nBasic loop:\n\n{{#each posts}}\n  <article>\n    <h2><a href=\"{{url}}\">{{name}}</a></h2>\n    <p>{{excerpt}}</p>\n  </article>\n{{/each}}\n\nLoop modifiers:\n\nModifierDescriptionExamplelimit=NMaximum items{{#each posts limit=6}}sort=\"field\"Sort by field{{#each posts sort=\"publishedAt\"}}order=\"asc|desc\"Sort direction{{#each posts sort=\"name\" order=\"asc\"}}featured=trueOnly featured items{{#each posts featured=true limit=3}}where=\"field.slug:{{slug}}\"Filter by relation{{#each posts where=\"author.slug:{{slug}}\"}}\n\nCombined modifiers:\n\n<!-- Latest 3 featured posts, newest first -->\n{{#each posts featured=true limit=3 sort=\"publishedAt\" order=\"desc\"}}\n  <article>{{name}}</article>\n{{/each}}"
      },
      {
        "title": "Loop Variables",
        "body": "Available only inside {{#each}} blocks:\n\nVariableDescriptionExample{{@index}}Zero-based index (0, 1, 2...)Item {{@index}}{{@first}}True for the first item{{#if @first}}hero{{/if}}{{@last}}True for the last item{{#unless @last}},{{/unless}}{{@length}}Total number of itemsShowing {{@length}} items\n\nDo NOT use loop variables outside {{#each}} blocks — they will produce warnings and undefined values.\n\n{{#each posts}}\n  {{#if @first}}\n    <div class=\"hero\">\n      <h1>{{name}}</h1>\n    </div>\n  {{else}}\n    <div class=\"card\">\n      <h3>{{name}}</h3>\n    </div>\n  {{/if}}\n{{/each}}"
      },
      {
        "title": "Conditionals — {{#if}}, {{#unless}}",
        "body": "<!-- Show if field has a value -->\n{{#if image}}\n  <img src=\"{{image}}\" alt=\"{{name}}\">\n{{/if}}\n\n<!-- Show if field has a value, with fallback -->\n{{#if thumbnail}}\n  <img src=\"{{thumbnail}}\" alt=\"\">\n{{else}}\n  <div class=\"placeholder\">No image</div>\n{{/if}}\n\n<!-- Show if field is empty/missing -->\n{{#unless posts}}\n  <p>No posts yet.</p>\n{{/unless}}"
      },
      {
        "title": "Equality & Comparison Helpers",
        "body": "<!-- Equal -->\n{{#if (eq status \"published\")}}\n  <span class=\"badge\">Published</span>\n{{/if}}\n\n<!-- Not equal — useful for \"Related Items\" excluding current item -->\n{{#unless (eq slug ../slug)}}\n  <a href=\"{{url}}\">{{name}}</a>\n{{/unless}}\n\n<!-- Numeric comparisons -->\n{{#if (lt @index 1)}}   <!-- Less than -->\n{{#if (gt @index 0)}}   <!-- Greater than -->\n{{#if (lte price 100)}} <!-- Less than or equal -->\n{{#if (gte stock 5)}}   <!-- Greater than or equal -->\n{{#if (ne status \"draft\")}} <!-- Not equal -->\n\nHero + grid layout pattern:\n\n{{#each posts}}\n  {{#if (lt @index 1)}}\n    <div class=\"hero\"><h1>{{name}}</h1></div>\n  {{else}}\n    {{#if (lt @index 4)}}\n      <div class=\"featured\"><h3>{{name}}</h3></div>\n    {{else}}\n      <div class=\"list-item\">{{name}}</div>\n    {{/if}}\n  {{/if}}\n{{/each}}"
      },
      {
        "title": "Relation Fields — Dot Notation",
        "body": "Access fields on related items using dot notation:\n\n{{#each posts}}\n  <article>\n    <h2>{{name}}</h2>\n    {{#if author}}\n      <span class=\"author\">By {{author.name}}</span>\n      {{#if author.photo}}\n        <img src=\"{{author.photo}}\" alt=\"{{author.name}}\">\n      {{/if}}\n    {{/if}}\n  </article>\n{{/each}}\n\nAvailable: {{relation.name}}, {{relation.slug}}, {{relation.url}}, {{relation.anyField}}."
      },
      {
        "title": "Parent Context — ../",
        "body": "Inside a loop, access the parent scope (the current page's item) with ../:\n\n<!-- On an author detail page, show only THIS author's posts -->\n<h1>{{name}}</h1>\n\n<h2>Posts by {{name}}</h2>\n{{#each posts}}\n  {{#if (eq author.name ../name)}}\n    <article>\n      <h2><a href=\"{{url}}\">{{name}}</a></h2>\n    </article>\n  {{/if}}\n{{/each}}"
      },
      {
        "title": "Nested Loops with @root.",
        "body": "When nesting loops, use @root. to reference root-level collections:\n\n{{#each categories}}\n  <h3>{{name}}</h3>\n  {{#each @root.posts where=\"category.slug:{{slug}}\"}}\n    <a href=\"{{url}}\">{{name}}</a>\n  {{/each}}\n{{/each}}"
      },
      {
        "title": "Inline Editing — data-edit-key (CRITICAL for Static Pages)",
        "body": "Without data-edit-key attributes, static pages have NO editable content in the CMS dashboard. Every text element that should be editable MUST have one.\n\n<!-- Static pages — REQUIRED for editable content -->\n<h1 data-edit-key=\"home-hero-title\">Welcome to Our Site</h1>\n<p data-edit-key=\"home-hero-subtitle\">We build amazing things.</p>\n<p data-edit-key=\"home-about-text\">Our story began in 2020...</p>\n\n<!-- Hierarchical naming for sections -->\n<section class=\"about\">\n  <h2 data-edit-key=\"about-section-title\">About Us</h2>\n  <p data-edit-key=\"about-section-paragraph-1\">First paragraph...</p>\n  <p data-edit-key=\"about-section-paragraph-2\">Second paragraph...</p>\n</section>\n\n<!-- CMS templates — optional, for hardcoded headers -->\n<h1 data-edit-key=\"blog-page-title\">Our Blog</h1>\n\nNaming convention: {page}-{section}-{element}\n\nExamples: home-hero-title, about-team-heading, contact-form-intro, services-cta-button\n\nRules:\n\nKeys must be unique across the entire site (not just the page).\nUse lowercase with hyphens.\nFor different pages, prefix with the page name.\nStatic pages without edit keys will appear in the CMS but have nothing editable."
      },
      {
        "title": "Video Embeds",
        "body": "{{#if video}}\n  <iframe\n    src=\"{{video}}\"\n    allowfullscreen\n    referrerpolicy=\"strict-origin-when-cross-origin\"\n    title=\"Video\"\n  ></iframe>\n{{/if}}\n\nThe referrerpolicy=\"strict-origin-when-cross-origin\" attribute is required for YouTube embeds — without it, videos may show Error 150/153."
      },
      {
        "title": "Images — Static vs CMS Content",
        "body": "There are two types of images and they are handled differently:\n\n1. Static/UI images — logos, icons, decorative backgrounds bundled with the site:\n\n<!-- KEEP these as static /public/ paths -->\n<img src=\"/public/images/logo.png\" alt=\"Company Logo\">\n<img src=\"/public/images/icons/arrow.svg\" alt=\"\">\n\n2. CMS content images — post images, team photos, product images managed through the CMS:\n\n<!-- USE CMS tokens — NEVER hardcode content image URLs -->\n{{#if image}}\n  <img src=\"{{image}}\" alt=\"{{name}}\">\n{{/if}}\n\nRule of thumb: If it's site branding/design → keep static. If it's content that changes per item → use CMS tokens.\n\nAlways wrap CMS images in {{#if}} — not every item may have an image:\n\n{{#if image}}\n  <img src=\"{{image}}\" alt=\"{{name}}\">\n{{else}}\n  <div class=\"placeholder-image\"></div>\n{{/if}}\n\nCommon mistake — mixing static and CMS images:\n\n<!-- WRONG: hardcoded image inside a CMS loop -->\n{{#each products}}\n  <img src=\"/images/product-placeholder.jpg\" alt=\"Product\">  <!-- BAD -->\n  <h2>{{name}}</h2>\n{{/each}}\n\n<!-- CORRECT: all content comes from CMS -->\n{{#each products}}\n  {{#if image}}\n    <img src=\"{{image}}\" alt=\"{{name}}\">\n  {{/if}}\n  <h2>{{name}}</h2>\n{{/each}}"
      },
      {
        "title": "Forms",
        "body": "<form data-form-name=\"contact\" action=\"/_forms/contact\" method=\"POST\">\n  <input type=\"text\" name=\"name\" placeholder=\"Your name\" required>\n  <input type=\"email\" name=\"email\" placeholder=\"Your email\" required>\n  <textarea name=\"message\" placeholder=\"Your message\" required></textarea>\n  <button type=\"submit\">Send Message</button>\n</form>\n\nRules:\n\ndata-form-name attribute is required.\naction must point to /_forms/{formName}.\nAll inputs must have name attributes.\nA submit button is required.\n\nCRITICAL: Remove Original Form Handlers\n\nIf the source site has JavaScript that handles form submissions, you MUST remove or replace it. Original site JS often does e.preventDefault() and shows a \"fake\" success toast — the data goes nowhere.\n\n// PROBLEM: This blocks real submissions!\nform.addEventListener('submit', (e) => {\n  e.preventDefault();\n  showToast('Message sent!');  // FAKE! Data not saved!\n});\n\nOption A (simplest): Remove the original JavaScript form handler entirely. The native <form action=\"/_forms/contact\" method=\"POST\"> will submit correctly.\n\nOption B (keep JS UX): Replace the handler with one that actually POSTs to FastMode:\n\nform.addEventListener('submit', async (e) => {\n  e.preventDefault();\n  const formName = form.dataset.formName;\n  const response = await fetch('/_forms/' + formName, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(Object.fromEntries(new FormData(form)))\n  });\n  if (response.ok) {\n    form.reset();\n    alert('Message sent!');  // NOW it's real!\n  }\n});"
      },
      {
        "title": "How Deployment Works",
        "body": "Upload: fastmode deploy site.zip reads the zip, validates it, and uploads it to the server.\nBuild: The server processes the package (renders templates, publishes pages). This happens asynchronously.\nWait: By default, deploy polls for build status every 3 seconds until the build completes or times out (default 2 minutes).\nResult: Success message with page count and version, or failure message with error details."
      },
      {
        "title": "Deploy Flags",
        "body": "FlagDescription--forceSkip the GitHub connection check. Use if the project has GitHub connected but you want to deploy via CLI anyway.--no-waitUpload only — don't wait for the build to finish. Useful for fire-and-forget.--timeout <ms>Custom build timeout in milliseconds. Default: 120000 (2 minutes)."
      },
      {
        "title": "Checking Build Status",
        "body": "After every deploy or content change, check the build status:\n\nfastmode status\n\nIf the build failed, status shows:\n\nThe error message\nBuild logs\nWhat went wrong\n\nAlways run fastmode status after deploying to verify the build succeeded.\n\nIf a build fails:\n\nRun fastmode status to see the error\nFix the issue (template errors, invalid tokens, missing files, etc.)\nRe-deploy with fastmode deploy site.zip"
      },
      {
        "title": "Deploy History",
        "body": "fastmode deploys                # Show last 10 deployments\nfastmode deploys --limit 5     # Show last 5\n\nShows status, version, duration, source, and errors for each deployment."
      },
      {
        "title": "Exit Codes",
        "body": "deploy: Exits 1 if the build fails (when waiting).\nstatus: Exits 1 if the latest deploy shows \"Failed\".\nBoth: Exit 1 if no project is specified."
      },
      {
        "title": "Validation",
        "body": "Always validate before deploying. Validation catches errors that would cause build failures."
      },
      {
        "title": "Validation Workflow",
        "body": "# 1. Validate the manifest\nfastmode validate manifest manifest.json\n\n# 2. Validate each template\nfastmode validate template pages/index.html -t static_page\nfastmode validate template templates/posts_index.html -t custom_index -c posts\nfastmode validate template templates/posts_detail.html -t custom_detail -c posts\n\n# 3. Validate the complete package\nfastmode validate package site.zip"
      },
      {
        "title": "What Gets Checked",
        "body": "Manifest validation:\n\nValid JSON syntax\npages array exists and is not empty\nHomepage (path /) exists\nAll file paths are valid\ncmsTemplates format is correct (flat keys, not nested)\n\nTemplate validation:\n\nBalanced tags: {{#each}} has matching {{/each}}, {{#if}} has {{/if}}\nIndex templates have at least one {{#each}} loop\nDetail templates have CMS tokens\nRich text fields use triple braces {{{field}}}\nLoop variables only used inside loops\nAsset paths use /public/ prefix\nForms have required attributes\nYouTube iframes have referrerpolicy\n\nPackage validation:\n\nmanifest.json exists at root\nAll referenced files exist in the zip\nAssets are in public/ (not assets/ or root)\nTemplates are in templates/ (not collections/)\nAll templates pass individual validation"
      },
      {
        "title": "Validation with Schema Check",
        "body": "Add -p to validate tokens against the actual project schema:\n\nfastmode validate template templates/posts_detail.html -t custom_detail -c posts -p \"My Project\"\n\nThis reports which tokens reference fields that don't exist in the schema yet, with instructions to create them via fastmode schema sync."
      },
      {
        "title": "1. Assets return 404",
        "body": "Problem: CSS, JS, or images don't load.\nCause: Files are in /assets/ instead of /public/, or paths don't include /public/.\nFix: Move all static files to the public/ folder. Reference them as /public/css/style.css."
      },
      {
        "title": "2. Rich text shows as raw HTML",
        "body": "Problem: Content displays <p>Hello</p> as text instead of rendering it.\nCause: Using double braces {{body}} on a rich text field.\nFix: Use triple braces {{{body}}} for all richText fields."
      },
      {
        "title": "3. Collection listing page is blank",
        "body": "Problem: Index template shows no items.\nCause: Missing {{#each collectionSlug}} loop.\nFix: Add a loop: {{#each posts}}...{{/each}}."
      },
      {
        "title": "4. All detail pages look the same",
        "body": "Problem: Every item page shows identical content.\nCause: Detail template has no CMS tokens — just static HTML.\nFix: Use tokens like {{name}}, {{{body}}}, {{image}} in the detail template."
      },
      {
        "title": "5. Manifest uses wrong format",
        "body": "Problem: Build fails with manifest errors.\nCause: Using nested objects or \"collections\" instead of flat \"cmsTemplates\" keys.\nFix: Use flat format: \"postsIndex\", \"postsIndexPath\", \"postsDetail\", \"postsDetailPath\"."
      },
      {
        "title": "6. Relation field is empty after create",
        "body": "Problem: Created an item with a relation field but it's null.\nCause: Used the item's name instead of its UUID.\nFix: Run fastmode items relations <collection> --field <field> to get the UUID, then use that."
      },
      {
        "title": "7. Forms don't submit",
        "body": "Problem: Form appears to submit (shows toast/alert) but no data is received.\nCause: Original JavaScript calls preventDefault() and shows a fake success message.\nFix: Remove any form JavaScript that blocks submission. Use data-form-name and action=\"/_forms/formName\"."
      },
      {
        "title": "8. Static pages can't be edited in CMS",
        "body": "Problem: Pages appear in the CMS but have no editable content.\nCause: Missing data-edit-key attributes on text elements.\nFix: Add data-edit-key=\"unique-key\" to every text element that should be editable."
      },
      {
        "title": "9. Deploy blocked by GitHub",
        "body": "Problem: deploy refuses to upload, says GitHub is connected.\nCause: Project has GitHub auto-deploy enabled.\nFix: Use --force flag: fastmode deploy site.zip --force."
      },
      {
        "title": "10. Build fails after deploy",
        "body": "Problem: Upload succeeds but build fails.\nFix: Run fastmode status to see the error. Common causes: invalid tokens, missing template files, malformed manifest. Fix the issue and re-deploy."
      },
      {
        "title": "11. Template URLs don't match manifest",
        "body": "Problem: Links in templates point to wrong paths.\nCause: Template hardcodes /posts/ but manifest sets \"postsIndexPath\": \"/blog\".\nFix: Make sure hardcoded links in templates match the paths defined in manifest.json."
      },
      {
        "title": "12. Loop variables undefined",
        "body": "Problem: {{@index}} or {{@first}} shows nothing.\nCause: Used outside of a {{#each}} block.\nFix: Only use loop variables inside {{#each}}...{{/each}}."
      },
      {
        "title": "13. Duplicate SEO meta tags",
        "body": "Problem: SEO tags show up twice in the rendered HTML.\nCause: HTML templates include <title>, <meta name=\"description\">, or Open Graph tags.\nFix: Remove all SEO tags from templates. FastMode manages them automatically via CMS Settings. See the SEO Tags section above."
      },
      {
        "title": "14. CSS background images and fonts broken",
        "body": "Problem: Background images or custom fonts don't load.\nCause: CSS files use relative paths like url('../images/bg.jpg') or url('../fonts/custom.woff').\nFix: Update all paths inside CSS files to use /public/ prefix: url('/public/images/bg.jpg'), url('/public/fonts/custom.woff2')."
      },
      {
        "title": "15. Hardcoded example content instead of CMS tokens",
        "body": "Problem: Index page shows static placeholder cards instead of real CMS data.\nCause: Template has hardcoded HTML cards instead of {{#each}} loops with CMS tokens.\nFix: Replace hardcoded content blocks with {{#each collection}}...{{/each}} loops using CMS field tokens."
      },
      {
        "title": "Pre-Deployment Checklist",
        "body": "Run through this checklist before every deploy:\n\nStructure:\n\nmanifest.json at package root\n Static pages in pages/ folder\n CMS templates in templates/ folder\n ALL assets in public/ folder (not assets/)\n\nSEO (CRITICAL):\n\nNO <title> tags in HTML\n NO <meta name=\"description\"> tags\n NO <meta property=\"og:*\"> tags\n NO <link rel=\"icon\"> tags\n\nManifest:\n\nHomepage page with \"path\": \"/\" exists\n CMS templates use flat cmsTemplates keys (NOT nested, NOT collections)\n Paths match original site URLs\n\nTemplates:\n\nIndex templates have {{#each}} loops\n Detail templates have CMS tokens ({{name}}, {{{body}}}, etc.)\n Rich text fields use triple braces {{{field}}}\n All {{#each}} have matching {{/each}}\n All {{#if}} have matching {{/if}}\n Static UI images use /public/ paths\n Content images use CMS tokens with {{#if}} wrappers\n\nStatic Pages:\n\ndata-edit-key on every editable text element\n Keys are unique across the entire site\n Forms have data-form-name and action=\"/_forms/{name}\"\n Original form JavaScript handlers removed or replaced\n\nAssets:\n\nAll HTML asset paths use /public/ prefix\n CSS background-image and font url() paths use /public/ prefix\n External URLs (Google Fonts, CDNs) unchanged\n\nValidation:\n\nfastmode validate manifest manifest.json\nfastmode validate template <each-template> -t <type> [-c <collection>]\nfastmode validate package site.zip"
      },
      {
        "title": "Error Handling & Exit Codes",
        "body": "All commands exit with code 0 on success and code 1 on failure."
      },
      {
        "title": "Commands that exit 1",
        "body": "ScenarioCommandsNo project specifiedAll project-scoped commandsFile not foundschema sync, items create -f, validate *Invalid JSONschema sync, items create -d, items update -dValidation errorsvalidate manifest, validate template, validate packageBuild faileddeploy (when waiting), statusDelete without --confirmitems delete"
      },
      {
        "title": "Error Messages",
        "body": "Error: No project specified.\nUse -p <id-or-name>, set FASTMODE_PROJECT env var, or run: fastmode use <project>\n\nError: File not found: schema.json\n\nError: Invalid JSON in --data argument\n\nError: Deletion requires the --confirm flag. This action cannot be undone."
      },
      {
        "title": "Authentication Errors",
        "body": "Most commands auto-trigger fastmode login if credentials are missing or expired. If authentication fails:\n\nRun fastmode login manually\nComplete the browser flow\nRetry the command"
      },
      {
        "title": "File Locations",
        "body": "PathPurpose~/.fastmode/credentials.jsonOAuth tokens (auto-created by login)~/.fastmode/config.jsonDefault project setting (created by use)\n\nBoth files have restricted permissions (0o600 — owner read/write only)."
      },
      {
        "title": "Notes",
        "body": "All project-scoped commands use your default project (set with fastmode use). Override with -p <name-or-id>.\nItem data (-d) must be valid JSON. For complex data, write a file and use -f data.json.\nRich text fields accept HTML content (e.g. <p>, <h2>, <ul>, <a>). Always use triple braces in templates.\nRelation fields require item IDs (UUIDs). Use fastmode items relations to find available IDs.\nThe --draft flag creates unpublished items. Use --publish/--unpublish to change status.\nEvery site gets free hosting, free SSL, and a .fastmode.ai subdomain. Custom domains can be configured.\nAfter deploying or making content changes, always run fastmode status to verify the build succeeded.\nUse fastmode examples <type> and fastmode guide [section] for built-in documentation and code snippets."
      },
      {
        "title": "Package Provenance",
        "body": "npm package: fastmode-cli\nSource code: github.com/arihgoldstein/fastmode-mcp\nWebsite: fastmode.ai\nAuthor: Arih Goldstein\nLicense: MIT"
      }
    ],
    "body": "FastMode CLI — Complete Agent Reference\n\nFastMode lets you create a live website, deploy it to the cloud, and manage all its content — entirely from the command line. One-time browser login for OAuth authentication, then every operation runs in the terminal. No local servers, no manual dashboards.\n\nFree cloud hosting — every site gets a live URL at yoursite.fastmode.ai\nFree SSL — HTTPS included automatically\nCustom domains — connect any domain (e.g. www.example.com)\nFull CMS — any content structure (blog, team, products, portfolios, anything)\nAgent-native — every operation works via CLI, zero human intervention needed\nTable of Contents\nEnd-to-End Workflow\nCRITICAL: Before You Build Anything\nWebsite Analysis\nCommand Reference\nProject Resolution\nSchema & Field Types\nContent Items\nClient Portal Management\nPackage Structure\nManifest Format\nTemplate Syntax (includes SEO rules, image handling, forms, inline editing)\nDeployment & Build Status\nValidation\nCommon Mistakes & How to Fix Them\nPre-Deployment Checklist\nError Handling & Exit Codes\nEnd-to-End Workflow\n\nThis is the complete sequence to go from nothing to a live website.\n\n# 1. Authenticate (one-time — credentials persist at ~/.fastmode/credentials.json)\nfastmode login\n\n# 2. Create a project (gets a free hosted URL instantly)\nfastmode projects create \"Acme Corp\"\nfastmode use \"Acme Corp\"\n\n# 3. Define content structure (write schema.json, then sync it)\nfastmode schema sync -f schema.json\n\n# 4. Add content\nfastmode items create posts -n \"Welcome\" -d '{\"title\": \"Welcome to Acme\", \"body\": \"<p>We build great things.</p>\"}'\nfastmode items create team -n \"Jane Doe\" -d '{\"role\": \"Founder\", \"bio\": \"<p>Visionary leader.</p>\"}'\n\n# 5. Build HTML templates with {{tokens}}, package into a zip, validate, deploy\nfastmode validate package site.zip\nfastmode deploy site.zip\n# Deploy waits for build to finish and reports success or failure with error details\n\n# 6. If the build failed, check what went wrong\nfastmode status\n# Fix the issue, then re-deploy\n\nCRITICAL: Before You Build Anything\n\nSTOP. Before writing ANY HTML, templates, or manifest.json, complete these steps.\n\nStep 1: Check for Existing Projects\nfastmode projects\n\n\nThis lists all the user's existing FastMode projects.\n\nStep 2: Decide — Existing or New Project\n\nIf projects exist: Ask the user: \"Is this website for one of your existing projects, or should I create a new one?\" Let the user choose.\n\nIf NO projects exist: This is a new user — ask: \"What would you like to name your new project?\"\n\nStep 3a: For EXISTING Projects\nUser selects the project from the list\nRun fastmode use \"Project Name\" to set it as default\nRun fastmode schema show to get the current collections and fields\nUse this schema to build templates with the correct field names\nStep 3b: For NEW Projects\nAsk for the project name if you don't have it\nRun fastmode projects create \"Project Name\"\nRun fastmode use \"Project Name\"\nYou'll create the schema later with fastmode schema sync\nOptionally generate sample content: fastmode generate-samples\nCheckpoint — Confirm Before Continuing\nRequirement\tHow to Get It\nProject selected/created\tfastmode projects / fastmode projects create\nDefault set\tfastmode use \"Project Name\"\nSchema known (existing)\tfastmode schema show\n\nIf you don't have a project set, DO NOT PROCEED. Go back to Step 1.\n\nWHY THIS MATTERS:\n\nFor existing projects: The schema determines which fields to use in templates — get it wrong and the build fails\nFor new projects: You need the project before you can deploy\nAlways: The user must confirm which project to use — never assume\nWebsite Analysis (Do This Before Writing Code)\n\nBefore writing any HTML or templates, analyze the site:\n\nMap ALL URLs — document every page path (/, /about, /blog, /blog/post-slug, etc.)\nCategorize each page — Static (fixed content), List (shows multiple items), or Detail (single item from a collection)\nIdentify collections — repeating content that should be CMS-managed (blog posts, team members, products, testimonials, etc.)\nDocument assets — all CSS, JS, image, and font file locations\nPRESERVE original URLs — if the site uses /resources for articles, keep /resources. Do NOT change it to /blog. Use the manifest's path configuration.\nCommand Reference\nAuthentication\nfastmode login                          # Open browser for OAuth device flow\nfastmode logout                         # Delete ~/.fastmode/credentials.json\nfastmode whoami                         # Show current user email and name\n\nlogin uses OAuth 2.0 device authorization flow: opens a browser window where the user approves access on fastmode.ai, then credentials are saved automatically. The browser is only needed for this one-time login step.\nOAuth scopes: The token grants access to the user's FastMode projects only (project management, schema editing, content CRUD, deployments). No third-party service access is requested.\nCredentials persist at ~/.fastmode/credentials.json with restricted file permissions (0o600 — owner read/write only). Treat this file as a sensitive secret.\nTokens auto-refresh. If a token expires, the next command will refresh it silently using the stored refresh token.\nIf credentials are missing or invalid, most commands will trigger the login flow automatically.\nlogout deletes ~/.fastmode/credentials.json and revokes the stored tokens.\nProjects\nfastmode projects                       # List all projects (default action)\nfastmode projects list                  # Same as above\nfastmode projects create \"Name\"         # Create a new project\nfastmode projects create \"Name\" --subdomain custom-sub  # Custom subdomain\nfastmode projects create \"Name\" --force                  # Skip similar-name check\nfastmode use <project>                  # Set default project for all commands\n\nprojects create checks for existing projects with similar names. Use --force to skip.\nSubdomain auto-generated from name if not provided (lowercase, hyphens, max 30 chars).\nuse stores the default in ~/.fastmode/config.json. Does NOT validate the project exists.\nSchema\nfastmode schema show                    # Show all collections and fields\nfastmode schema show -p \"Project Name\"  # Specify project\nfastmode schema sync -f schema.json     # Create collections and fields from JSON file\nfastmode schema field-types             # List all available field types (no auth needed)\n\nschema show requires authentication and a project.\nschema sync reads a local JSON file and creates/updates the schema. Skips duplicates. Two-phase: creates collections first, then fields (handles relation dependencies).\nschema field-types works without authentication.\nContent Items\nfastmode items list <collection>                          # List all items\nfastmode items list posts --limit 10 --sort publishedAt --order desc\nfastmode items get <collection> <slug>                    # Get single item\nfastmode items create <collection> -n \"Name\" -d '{\"field\": \"value\"}'\nfastmode items create posts -n \"Title\" -f data.json       # Data from file\nfastmode items create posts -n \"Draft Post\" -d '{}' --draft\nfastmode items update <collection> <slug> -d '{\"field\": \"new value\"}'\nfastmode items update posts my-post -n \"New Title\"\nfastmode items update posts my-post --publish             # Publish a draft\nfastmode items update posts my-post --unpublish           # Revert to draft\nfastmode items delete <collection> <slug> --confirm       # REQUIRES --confirm\nfastmode items relations <collection>                     # Show linkable items for relation fields\nfastmode items relations posts --field author             # Options for specific field\n\n\nSee the Content Items section below for detailed rules on data formats, relation fields, and drafts.\n\nClient Portal Management\nfastmode clients list                              # List portal clients with access\nfastmode clients invite client@example.com         # Invite with default permissions\nfastmode clients invite client@example.com -n \"Jane\" --permissions cms.read,cms.write\nfastmode clients invitations                       # List pending invitations\nfastmode clients update-permissions <accessId> --permissions cms.read,editor\nfastmode clients revoke <accessId> --confirm       # REQUIRES --confirm\nfastmode clients cancel-invite <invitationId> --confirm  # REQUIRES --confirm\n\n\nSee the Client Portal Management section below for details on permissions, invite flow, and examples.\n\nDeployment & Build Status\nfastmode deploy site.zip                # Deploy and wait for build to finish\nfastmode deploy site.zip --force        # Skip GitHub sync check\nfastmode deploy site.zip --no-wait      # Upload only, don't wait for build\nfastmode deploy site.zip --timeout 300000  # Custom timeout in ms (default: 120000)\nfastmode status                         # Check current build/deploy status\nfastmode deploys                        # List deployment history\nfastmode deploys --limit 5             # Limit number of results\n\n\nSee Deployment & Build Status below for the full deploy lifecycle.\n\nValidation\nfastmode validate manifest manifest.json\nfastmode validate template index.html -t custom_index\nfastmode validate template post.html -t custom_detail -c posts\nfastmode validate template post.html -t custom_detail -c posts -p \"My Project\"\nfastmode validate template about.html -t static_page\nfastmode validate package site.zip\n\nTemplate types: custom_index (collection listing), custom_detail (single item), static_page (fixed page).\n-c specifies the collection slug (required for custom_index and custom_detail).\n-p validates tokens against the actual project schema (reports missing fields).\nAll validation commands exit with code 1 on errors — safe for CI/CD pipelines.\nDocumentation & Examples\nfastmode examples <type>                # Code examples for a specific pattern\nfastmode guide                          # Full website conversion guide\nfastmode guide templates                # Template syntax guide\nfastmode guide common_mistakes          # Common pitfalls to avoid\nfastmode generate-samples               # Generate placeholder content for empty collections\nfastmode generate-samples -c posts team # Specific collections only\n\n\nAvailable example types: manifest_basic, manifest_custom_paths, blog_index_template, blog_post_template, team_template, downloads_template, form_handling, asset_paths, data_edit_keys, each_loop, conditional_if, nested_fields, featured_posts, parent_context, equality_comparison, comparison_helpers, youtube_embed, nested_collection_loop, loop_variables, common_mistakes.\n\nAvailable guide sections: full, first_steps, analysis, structure, seo, manifest, templates, tokens, forms, assets, checklist, common_mistakes.\n\nProject Resolution\n\nEvery project-scoped command (schema, items, deploy, status, etc.) needs a project. Resolution order:\n\n-p / --project flag — explicit on the command: -p \"My Project\" or -p abc123-uuid\nFASTMODE_PROJECT environment variable — set in shell: export FASTMODE_PROJECT=\"My Project\"\nDefault project — saved by fastmode use \"My Project\" in ~/.fastmode/config.json\n\nIf none is set, the command prints an error and exits with code 1:\n\nError: No project specified.\nUse -p <id-or-name>, set FASTMODE_PROJECT env var, or run: fastmode use <project>\n\n\nProject identifiers can be:\n\nUUID — used directly (e.g. 550e8400-e29b-41d4-a716-446655440000)\nProject name — resolved via API (exact match first, then partial match, case-insensitive)\nSchema & Field Types\nCreating a Schema\n\nWrite a schema.json file and sync it:\n\nfastmode schema sync -f schema.json\n\nschema.json Format\n{\n  \"collections\": [\n    {\n      \"slug\": \"posts\",\n      \"name\": \"Blog Posts\",\n      \"nameSingular\": \"Blog Post\",\n      \"fields\": [\n        { \"slug\": \"title\", \"name\": \"Title\", \"type\": \"text\", \"isRequired\": true },\n        { \"slug\": \"excerpt\", \"name\": \"Excerpt\", \"type\": \"textarea\" },\n        { \"slug\": \"body\", \"name\": \"Body\", \"type\": \"richText\" },\n        { \"slug\": \"featured-image\", \"name\": \"Featured Image\", \"type\": \"image\" },\n        { \"slug\": \"category\", \"name\": \"Category\", \"type\": \"select\", \"options\": \"News, Tutorial, Update\" },\n        { \"slug\": \"tags\", \"name\": \"Tags\", \"type\": \"multiSelect\", \"options\": \"JavaScript, Python, DevOps, AI\" },\n        { \"slug\": \"featured\", \"name\": \"Featured\", \"type\": \"boolean\" },\n        { \"slug\": \"author\", \"name\": \"Author\", \"type\": \"relation\", \"referenceCollection\": \"team\" }\n      ]\n    },\n    {\n      \"slug\": \"team\",\n      \"name\": \"Team Members\",\n      \"nameSingular\": \"Team Member\",\n      \"fields\": [\n        { \"slug\": \"role\", \"name\": \"Role\", \"type\": \"text\" },\n        { \"slug\": \"bio\", \"name\": \"Bio\", \"type\": \"richText\" },\n        { \"slug\": \"photo\", \"name\": \"Photo\", \"type\": \"image\" },\n        { \"slug\": \"email\", \"name\": \"Email\", \"type\": \"email\" }\n      ]\n    }\n  ]\n}\n\n\nTo add fields to existing collections, use fieldsToAdd:\n\n{\n  \"fieldsToAdd\": [\n    {\n      \"collectionSlug\": \"posts\",\n      \"fields\": [\n        { \"slug\": \"reading-time\", \"name\": \"Reading Time\", \"type\": \"number\" }\n      ]\n    }\n  ]\n}\n\n\nYou can combine collections and fieldsToAdd in the same file. Duplicate collections and fields are automatically skipped.\n\nAvailable Field Types\nType\tDescription\tTemplate Usage\tNotes\ntext\tSingle-line text\t{{field}}\tTitles, names, short strings\ntextarea\tMulti-line plain text\t{{field}}\tDescriptions, excerpts\nrichText\tFormatted HTML content\t{{{field}}}\tMUST use triple braces\nnumber\tNumeric value\t{{field}}\tPrices, counts, order\nboolean\tTrue/false toggle\t{{#if field}}\tToggles, flags\ndate\tDate only\t{{field}}\tBirth dates, event dates\ndatetime\tDate and time\t{{field}}\tTimestamps\nimage\tImage file/URL\t{{field}}\tRenders as URL\nfile\tDownloadable file (max 10MB)\t{{field}}\tLink as <a href=\"{{field}}\" download>\nurl\tWeb link\t{{field}}\tExternal URLs\nvideoEmbed\tYouTube/Vimeo/Wistia/Loom\t{{field}}\tEmbed URL\nemail\tEmail with validation\t{{field}}\tValidated email addresses\nselect\tSingle dropdown\t{{field}}\tRequires \"options\": \"A, B, C\"\nmultiSelect\tMultiple selections\t{{field}}\tRequires \"options\": \"A, B, C\"\nrelation\tLink to another collection\t{{field.name}}\tRequires \"referenceCollection\": \"slug\"\nRelation Fields — CRITICAL\n\nRelation fields link items between collections (e.g. a post has an author from the team collection). When creating or updating items with relation fields:\n\nYou MUST use the item's UUID, not its name or slug\nUse fastmode items relations <collection> to get the available IDs\nExample: fastmode items relations posts --field author shows team member IDs\n# First, find the author's item ID\nfastmode items relations posts --field author\n# Output shows: ID: 550e8400-..., Name: Jane Doe, Slug: jane-doe\n\n# Then use that ID when creating a post\nfastmode items create posts -n \"My Post\" -d '{\"title\": \"My Post\", \"author\": \"550e8400-e29b-41d4-a716-446655440000\"}'\n\n\nWRONG: \"author\": \"Jane Doe\" — this will NOT work. CORRECT: \"author\": \"550e8400-e29b-41d4-a716-446655440000\" — use the UUID.\n\nContent Items\nCreating Items\nfastmode items create <collection> -n \"Item Name\" -d '{\"field\": \"value\"}'\n\nFlag\tDescription\n-n, --name <name>\tRequired. Item name/title.\n-s, --slug <slug>\tURL slug. Auto-generated from name if omitted.\n-d, --data <json>\tField data as JSON string.\n-f, --file <path>\tRead field data from a JSON file (takes precedence over -d).\n-p, --project <id>\tProject ID or name.\n--draft\tCreate as unpublished draft.\n\nData rules:\n\n-d value must be valid JSON. Keys are field slugs.\n-f reads from a JSON file. If both -f and -d are given, -f wins.\nIf neither -d nor -f is given, the item is created with just the name (no field data).\nRich text fields accept raw HTML: \"body\": \"<h2>Title</h2><p>Content here.</p>\"\nRelation fields require UUIDs (see above).\nWithout --draft, items are published immediately (publishedAt set to now).\nUpdating Items\nfastmode items update <collection> <slug> -d '{\"field\": \"new value\"}'\n\nFlag\tDescription\n-n, --name <name>\tNew name/title.\n-d, --data <json>\tUpdated fields as JSON. Only provided fields change — others are preserved.\n-f, --file <path>\tRead updated data from a JSON file.\n-p, --project <id>\tProject ID or name.\n--publish\tSet publishedAt to now (make item live).\n--unpublish\tSet publishedAt to null (revert to draft).\n\nUpdate is a partial merge. Only the fields you provide in -d are changed. All other fields remain as they are.\n\nDeleting Items\nfastmode items delete <collection> <slug> --confirm\n\n\nThe --confirm flag is required. Without it, the command refuses to run and exits with code 1. This is a safety measure — deletion is permanent and cannot be undone.\n\nAlways ask the user for confirmation before deleting.\n\nDraft / Publish Mechanics\nAction\tCommand\nCreate as published (default)\tfastmode items create posts -n \"Title\" -d '{...}'\nCreate as draft\tfastmode items create posts -n \"Title\" -d '{...}' --draft\nPublish a draft\tfastmode items update posts my-slug --publish\nUnpublish (revert to draft)\tfastmode items update posts my-slug --unpublish\nDraft items have publishedAt: null and are not visible on the live site.\nPublished items have a publishedAt timestamp and appear on the live site.\nWithout --draft, new items are published immediately.\nClient Portal Management\n\nThe client portal lets you give external clients (your customers, collaborators) limited access to manage content on your FastMode site. Clients get their own login, separate from your admin account, with configurable permissions.\n\nHow It Works\nYou invite a client by email — they receive a unique invite link\nClient clicks the link — creates a password and gets portal access\nClient manages content — based on the permissions you assigned\nYou control access — update permissions or revoke access at any time\n\nThe portal is auto-enabled on the project when you send the first invitation. No manual setup needed.\n\nAvailable Permissions\nPermission\tDescription\ncms.read\tView collection items\ncms.write\tCreate, edit, archive, and delete items\neditor\tAccess the visual editor\nforms.read\tView form submissions\ndns\tManage DNS settings\napi\tAccess API and integrations\nnotifications\tManage notification rules\nbilling\tView plans and manage billing\n\nDefault permissions (used when none specified): cms.read, cms.write, editor, forms.read\n\nInviting Clients\n# Invite with default permissions\nfastmode clients invite client@example.com\n\n# Invite with a name\nfastmode clients invite client@example.com -n \"Jane Smith\"\n\n# Invite with specific permissions\nfastmode clients invite client@example.com -n \"Jane Smith\" --permissions cms.read,forms.read\n\n# Invite with all permissions\nfastmode clients invite client@example.com --permissions cms.read,cms.write,editor,forms.read,dns,api,notifications,billing\n\n\nThe command returns an invite URL — share this with the client. The link expires in 7 days.\n\nImportant:\n\nEach email can only be invited once per project\nIf a client already has access, the invite will fail\nIf a pending invitation already exists for the email, the invite will fail\nListing Clients and Invitations\n# See who has portal access\nfastmode clients list\n\n# See pending (unaccepted) invitations\nfastmode clients invitations\n\n\nclients list shows the access ID for each client — you need this ID to update permissions or revoke access.\n\nUpdating Permissions\n# First, get the access ID from the list\nfastmode clients list\n\n# Update permissions (replaces ALL existing permissions)\nfastmode clients update-permissions <accessId> --permissions cms.read,cms.write,editor\n\n\nPermissions are replaced entirely — if a client had cms.read,cms.write,editor,forms.read and you set --permissions cms.read, they will ONLY have cms.read.\n\nRevoking Access\n# Revoke a client's portal access (requires --confirm)\nfastmode clients revoke <accessId> --confirm\n\n\nThe --confirm flag is required. Without it, the command refuses to run.\n\nAlways ask the user for confirmation before revoking access.\n\nRevoking access is a soft delete — the client's account still exists but they cannot access this project's portal. Their active sessions are terminated immediately.\n\nCanceling Invitations\n# Cancel a pending invitation (requires --confirm)\nfastmode clients cancel-invite <invitationId> --confirm\n\n\nThe invitation link will no longer work. Use fastmode clients invitations to get the invitation ID.\n\nTypical Workflow\n# 1. Invite your client\nfastmode clients invite designer@agency.com -n \"Design Agency\" --permissions cms.read,cms.write,editor\n\n# 2. Share the invite URL from the output with the client\n\n# 3. Later, check who has access\nfastmode clients list\n\n# 4. Restrict a client to read-only\nfastmode clients update-permissions abc12345 --permissions cms.read\n\n# 5. Remove a client who no longer needs access\nfastmode clients revoke abc12345 --confirm\n\nPackage Structure\n\nThe deployment package is a .zip file with this exact structure:\n\nsite.zip\n├── manifest.json              # REQUIRED — defines pages and CMS templates\n├── pages/                     # Static HTML pages\n│   ├── index.html             # Homepage (REQUIRED — must have path \"/\")\n│   ├── about.html\n│   └── contact.html\n├── templates/                 # CMS-powered templates (if using collections)\n│   ├── posts_index.html       # Blog listing page\n│   ├── posts_detail.html      # Single blog post page\n│   └── team_index.html        # Team listing page\n└── public/                    # ALL static assets (CSS, JS, images, fonts)\n    ├── css/\n    │   └── style.css\n    ├── js/\n    │   └── main.js\n    └── images/\n        ├── logo.png\n        └── favicon.ico\n\nStrict Rules\nmanifest.json MUST be at the root of the zip.\nStatic pages go in pages/. One HTML file per page.\nCMS templates go in templates/. Convention: {collection}_index.html and {collection}_detail.html.\nALL static assets go in public/. CSS, JavaScript, images, fonts — everything.\nReference assets with /public/ prefix in HTML. Example: <link href=\"/public/css/style.css\" rel=\"stylesheet\">.\nA homepage is required. One page must have \"path\": \"/\" in the manifest.\nCritical: Asset Paths\n\nWRONG — will 404:\n\n<link href=\"/assets/css/style.css\" rel=\"stylesheet\">\n<link href=\"/css/style.css\" rel=\"stylesheet\">\n<link href=\"../css/style.css\" rel=\"stylesheet\">\n<script src=\"js/main.js\"></script>\n\n\nCORRECT:\n\n<link href=\"/public/css/style.css\" rel=\"stylesheet\">\n<script src=\"/public/js/main.js\"></script>\n<img src=\"/public/images/logo.png\" alt=\"Logo\">\n\n\nThis also applies inside CSS files — background images AND fonts:\n\n/* WRONG */\nbackground-image: url('../images/bg.jpg');\nbackground-image: url('images/bg.jpg');\nsrc: url('../fonts/custom.woff2');\n\n/* CORRECT */\nbackground-image: url('/public/images/bg.jpg');\nsrc: url('/public/fonts/custom.woff2');\n\n\nAsset path conversion table:\n\nOriginal Path\tConverted Path\ncss/style.css\t/public/css/style.css\n../css/style.css\t/public/css/style.css\n./images/logo.png\t/public/images/logo.png\n/images/logo.png\t/public/images/logo.png\n../fonts/custom.woff\t/public/fonts/custom.woff\n\nExternal URLs (Google Fonts, CDNs, etc.) stay unchanged.\n\nManifest Format\n\nThe manifest.json file defines the site structure. It uses a FLAT format for CMS templates (not nested).\n\nBasic Example (Static Only)\n{\n  \"pages\": [\n    { \"path\": \"/\", \"file\": \"pages/index.html\", \"title\": \"Home\" },\n    { \"path\": \"/about\", \"file\": \"pages/about.html\", \"title\": \"About\" },\n    { \"path\": \"/contact\", \"file\": \"pages/contact.html\", \"title\": \"Contact\" }\n  ]\n}\n\nWith CMS Collections\n{\n  \"pages\": [\n    { \"path\": \"/\", \"file\": \"pages/index.html\", \"title\": \"Home\" },\n    { \"path\": \"/about\", \"file\": \"pages/about.html\", \"title\": \"About\" }\n  ],\n  \"cmsTemplates\": {\n    \"postsIndex\": \"templates/posts_index.html\",\n    \"postsIndexPath\": \"/blog\",\n    \"postsDetail\": \"templates/posts_detail.html\",\n    \"postsDetailPath\": \"/blog\",\n    \"teamIndex\": \"templates/team_index.html\",\n    \"teamIndexPath\": \"/team\"\n  }\n}\n\nCMS Template Keys — FLAT Format\n\nEach collection needs 2-4 keys in cmsTemplates. The format is {collectionSlug}Index, {collectionSlug}IndexPath, {collectionSlug}Detail, {collectionSlug}DetailPath.\n\nKey\tRequired\tDescription\n{slug}Index\tYes\tPath to the collection listing template file\n{slug}IndexPath\tYes\tURL path for the listing page (e.g. /blog)\n{slug}Detail\tNo\tPath to the single item template file\n{slug}DetailPath\tNo\tURL path prefix for item pages (e.g. /blog → /blog/item-slug)\n\nExample for a \"services\" collection:\n\n\"cmsTemplates\": {\n  \"servicesIndex\": \"templates/services_index.html\",\n  \"servicesIndexPath\": \"/services\",\n  \"servicesDetail\": \"templates/services_detail.html\",\n  \"servicesDetailPath\": \"/services\"\n}\n\nCommon Manifest Mistakes — AI Agents Frequently Get This Wrong\n\nAI agents frequently use a nested object format or \"collections\" key that FastMode does NOT support. Read carefully.\n\nWRONG — using \"collections\" key (MOST COMMON AI MISTAKE):\n\n{\n  \"collections\": {\n    \"posts\": {\n      \"indexPath\": \"/blog\",\n      \"indexFile\": \"collections/posts/index.html\",\n      \"detailPath\": \"/blog/:slug\",\n      \"detailFile\": \"collections/posts/detail.html\"\n    }\n  }\n}\n\n\nWRONG — nested objects inside cmsTemplates:\n\n\"cmsTemplates\": {\n  \"posts\": {\n    \"indexPath\": \"/blog\",\n    \"detailPath\": \"/blog\"\n  }\n}\n\n\nWRONG — singular slug names:\n\n\"postIndex\": \"...\"   // Should be \"postsIndex\"\n\"postDetail\": \"...\"  // Should be \"postsDetail\"\n\n\nCORRECT — flat keys using cmsTemplates, matching the collection slug exactly:\n\n\"cmsTemplates\": {\n  \"postsIndex\": \"templates/posts_index.html\",\n  \"postsIndexPath\": \"/blog\",\n  \"postsDetail\": \"templates/posts_detail.html\",\n  \"postsDetailPath\": \"/blog\"\n}\n\n\nKey rules:\n\nUse cmsTemplates, NOT collections\nUse FLAT keys: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath\nDo NOT nest objects inside collection names\nUse fastmode validate manifest manifest.json to catch these errors before deploying\nOptional: Head/Body Injection\n{\n  \"pages\": [...],\n  \"cmsTemplates\": {...},\n  \"defaultHeadHtml\": \"<link rel=\\\"stylesheet\\\" href=\\\"/public/css/global.css\\\">\",\n  \"defaultBodyEndHtml\": \"<script src=\\\"/public/js/analytics.js\\\"></script>\"\n}\n\nTemplate Syntax\n\nFastMode templates use Handlebars-style tokens. There are three types of templates:\n\nStatic pages (pages/): Fixed HTML with optional data-edit-key attributes for inline CMS editing and optional {{#each}} loops for dynamic content.\nIndex templates (templates/): Collection listing pages. MUST contain at least one {{#each collectionSlug}} loop.\nDetail templates (templates/): Single item pages. MUST contain CMS tokens like {{name}}, {{{body}}}, etc.\nSEO Tags — Do NOT Include\n\nFastMode automatically manages all SEO meta tags. Including them in your HTML will cause duplicate tags (bad for SEO ranking). Remove ALL of these from your templates:\n\nTag\tWhy to Remove\n<title>...</title>\tManaged via CMS Settings\n<meta name=\"description\">\tManaged via CMS Settings\n<meta name=\"keywords\">\tManaged via CMS Settings\n<meta property=\"og:*\">\tOpen Graph auto-generated\n<meta name=\"twitter:*\">\tTwitter cards auto-generated\n<link rel=\"icon\">\tFavicon managed in settings\n<link rel=\"shortcut icon\">\tFavicon managed in settings\n<link rel=\"apple-touch-icon\">\tManaged by FastMode\n<meta name=\"google-site-verification\">\tManaged in settings\n\nCorrect <head> structure:\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <!-- SEO managed by Fast Mode — do not add title, description, or OG tags -->\n  <link rel=\"stylesheet\" href=\"/public/css/style.css\">\n  <!-- External fonts, scripts, etc. are fine -->\n  <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap\" rel=\"stylesheet\">\n</head>\n\nBuilt-in Fields (Every Item Has These)\nToken\tDescription\tExample\n{{name}}\tItem name/title\t<h1>{{name}}</h1>\n{{slug}}\tURL slug\t<a href=\"/posts/{{slug}}\">\n{{url}}\tFull URL to detail page\t<a href=\"{{url}}\">Read more</a>\n{{publishedAt}}\tPublish date\t<time>{{publishedAt}}</time>\n{{createdAt}}\tCreation date\t\n{{updatedAt}}\tLast modified date\t\nRegular Fields — Double Braces {{field}}\n\nUsed for text, number, date, image, url, email, select, boolean fields:\n\n<h1>{{name}}</h1>\n<p>{{excerpt}}</p>\n<img src=\"{{featured-image}}\" alt=\"{{name}}\">\n<span>Category: {{category}}</span>\n<a href=\"{{website-url}}\">Visit</a>\n\nRich Text Fields — Triple Braces {{{field}}}\n\nCRITICAL: Rich text fields contain HTML. You MUST use triple braces {{{ }}} so the HTML renders correctly. Double braces will escape the HTML and display raw tags as text.\n\n<!-- CORRECT — HTML renders properly -->\n<div class=\"content\">{{{body}}}</div>\n<div class=\"bio\">{{{bio}}}</div>\n\n<!-- WRONG — HTML appears as escaped text like &lt;p&gt;Hello&lt;/p&gt; -->\n<div class=\"content\">{{body}}</div>\n\nLoops — {{#each collection}}\n\nUsed in index templates and static pages to iterate over collection items.\n\nBasic loop:\n\n{{#each posts}}\n  <article>\n    <h2><a href=\"{{url}}\">{{name}}</a></h2>\n    <p>{{excerpt}}</p>\n  </article>\n{{/each}}\n\n\nLoop modifiers:\n\nModifier\tDescription\tExample\nlimit=N\tMaximum items\t{{#each posts limit=6}}\nsort=\"field\"\tSort by field\t{{#each posts sort=\"publishedAt\"}}\norder=\"asc|desc\"\tSort direction\t{{#each posts sort=\"name\" order=\"asc\"}}\nfeatured=true\tOnly featured items\t{{#each posts featured=true limit=3}}\nwhere=\"field.slug:{{slug}}\"\tFilter by relation\t{{#each posts where=\"author.slug:{{slug}}\"}}\n\nCombined modifiers:\n\n<!-- Latest 3 featured posts, newest first -->\n{{#each posts featured=true limit=3 sort=\"publishedAt\" order=\"desc\"}}\n  <article>{{name}}</article>\n{{/each}}\n\nLoop Variables\n\nAvailable only inside {{#each}} blocks:\n\nVariable\tDescription\tExample\n{{@index}}\tZero-based index (0, 1, 2...)\tItem {{@index}}\n{{@first}}\tTrue for the first item\t{{#if @first}}hero{{/if}}\n{{@last}}\tTrue for the last item\t{{#unless @last}},{{/unless}}\n{{@length}}\tTotal number of items\tShowing {{@length}} items\n\nDo NOT use loop variables outside {{#each}} blocks — they will produce warnings and undefined values.\n\n{{#each posts}}\n  {{#if @first}}\n    <div class=\"hero\">\n      <h1>{{name}}</h1>\n    </div>\n  {{else}}\n    <div class=\"card\">\n      <h3>{{name}}</h3>\n    </div>\n  {{/if}}\n{{/each}}\n\nConditionals — {{#if}}, {{#unless}}\n<!-- Show if field has a value -->\n{{#if image}}\n  <img src=\"{{image}}\" alt=\"{{name}}\">\n{{/if}}\n\n<!-- Show if field has a value, with fallback -->\n{{#if thumbnail}}\n  <img src=\"{{thumbnail}}\" alt=\"\">\n{{else}}\n  <div class=\"placeholder\">No image</div>\n{{/if}}\n\n<!-- Show if field is empty/missing -->\n{{#unless posts}}\n  <p>No posts yet.</p>\n{{/unless}}\n\nEquality & Comparison Helpers\n<!-- Equal -->\n{{#if (eq status \"published\")}}\n  <span class=\"badge\">Published</span>\n{{/if}}\n\n<!-- Not equal — useful for \"Related Items\" excluding current item -->\n{{#unless (eq slug ../slug)}}\n  <a href=\"{{url}}\">{{name}}</a>\n{{/unless}}\n\n<!-- Numeric comparisons -->\n{{#if (lt @index 1)}}   <!-- Less than -->\n{{#if (gt @index 0)}}   <!-- Greater than -->\n{{#if (lte price 100)}} <!-- Less than or equal -->\n{{#if (gte stock 5)}}   <!-- Greater than or equal -->\n{{#if (ne status \"draft\")}} <!-- Not equal -->\n\n\nHero + grid layout pattern:\n\n{{#each posts}}\n  {{#if (lt @index 1)}}\n    <div class=\"hero\"><h1>{{name}}</h1></div>\n  {{else}}\n    {{#if (lt @index 4)}}\n      <div class=\"featured\"><h3>{{name}}</h3></div>\n    {{else}}\n      <div class=\"list-item\">{{name}}</div>\n    {{/if}}\n  {{/if}}\n{{/each}}\n\nRelation Fields — Dot Notation\n\nAccess fields on related items using dot notation:\n\n{{#each posts}}\n  <article>\n    <h2>{{name}}</h2>\n    {{#if author}}\n      <span class=\"author\">By {{author.name}}</span>\n      {{#if author.photo}}\n        <img src=\"{{author.photo}}\" alt=\"{{author.name}}\">\n      {{/if}}\n    {{/if}}\n  </article>\n{{/each}}\n\n\nAvailable: {{relation.name}}, {{relation.slug}}, {{relation.url}}, {{relation.anyField}}.\n\nParent Context — ../\n\nInside a loop, access the parent scope (the current page's item) with ../:\n\n<!-- On an author detail page, show only THIS author's posts -->\n<h1>{{name}}</h1>\n\n<h2>Posts by {{name}}</h2>\n{{#each posts}}\n  {{#if (eq author.name ../name)}}\n    <article>\n      <h2><a href=\"{{url}}\">{{name}}</a></h2>\n    </article>\n  {{/if}}\n{{/each}}\n\nNested Loops with @root.\n\nWhen nesting loops, use @root. to reference root-level collections:\n\n{{#each categories}}\n  <h3>{{name}}</h3>\n  {{#each @root.posts where=\"category.slug:{{slug}}\"}}\n    <a href=\"{{url}}\">{{name}}</a>\n  {{/each}}\n{{/each}}\n\nInline Editing — data-edit-key (CRITICAL for Static Pages)\n\nWithout data-edit-key attributes, static pages have NO editable content in the CMS dashboard. Every text element that should be editable MUST have one.\n\n<!-- Static pages — REQUIRED for editable content -->\n<h1 data-edit-key=\"home-hero-title\">Welcome to Our Site</h1>\n<p data-edit-key=\"home-hero-subtitle\">We build amazing things.</p>\n<p data-edit-key=\"home-about-text\">Our story began in 2020...</p>\n\n<!-- Hierarchical naming for sections -->\n<section class=\"about\">\n  <h2 data-edit-key=\"about-section-title\">About Us</h2>\n  <p data-edit-key=\"about-section-paragraph-1\">First paragraph...</p>\n  <p data-edit-key=\"about-section-paragraph-2\">Second paragraph...</p>\n</section>\n\n<!-- CMS templates — optional, for hardcoded headers -->\n<h1 data-edit-key=\"blog-page-title\">Our Blog</h1>\n\n\nNaming convention: {page}-{section}-{element}\n\nExamples: home-hero-title, about-team-heading, contact-form-intro, services-cta-button\n\nRules:\n\nKeys must be unique across the entire site (not just the page).\nUse lowercase with hyphens.\nFor different pages, prefix with the page name.\nStatic pages without edit keys will appear in the CMS but have nothing editable.\nVideo Embeds\n{{#if video}}\n  <iframe\n    src=\"{{video}}\"\n    allowfullscreen\n    referrerpolicy=\"strict-origin-when-cross-origin\"\n    title=\"Video\"\n  ></iframe>\n{{/if}}\n\n\nThe referrerpolicy=\"strict-origin-when-cross-origin\" attribute is required for YouTube embeds — without it, videos may show Error 150/153.\n\nImages — Static vs CMS Content\n\nThere are two types of images and they are handled differently:\n\n1. Static/UI images — logos, icons, decorative backgrounds bundled with the site:\n\n<!-- KEEP these as static /public/ paths -->\n<img src=\"/public/images/logo.png\" alt=\"Company Logo\">\n<img src=\"/public/images/icons/arrow.svg\" alt=\"\">\n\n\n2. CMS content images — post images, team photos, product images managed through the CMS:\n\n<!-- USE CMS tokens — NEVER hardcode content image URLs -->\n{{#if image}}\n  <img src=\"{{image}}\" alt=\"{{name}}\">\n{{/if}}\n\n\nRule of thumb: If it's site branding/design → keep static. If it's content that changes per item → use CMS tokens.\n\nAlways wrap CMS images in {{#if}} — not every item may have an image:\n\n{{#if image}}\n  <img src=\"{{image}}\" alt=\"{{name}}\">\n{{else}}\n  <div class=\"placeholder-image\"></div>\n{{/if}}\n\n\nCommon mistake — mixing static and CMS images:\n\n<!-- WRONG: hardcoded image inside a CMS loop -->\n{{#each products}}\n  <img src=\"/images/product-placeholder.jpg\" alt=\"Product\">  <!-- BAD -->\n  <h2>{{name}}</h2>\n{{/each}}\n\n<!-- CORRECT: all content comes from CMS -->\n{{#each products}}\n  {{#if image}}\n    <img src=\"{{image}}\" alt=\"{{name}}\">\n  {{/if}}\n  <h2>{{name}}</h2>\n{{/each}}\n\nForms\n<form data-form-name=\"contact\" action=\"/_forms/contact\" method=\"POST\">\n  <input type=\"text\" name=\"name\" placeholder=\"Your name\" required>\n  <input type=\"email\" name=\"email\" placeholder=\"Your email\" required>\n  <textarea name=\"message\" placeholder=\"Your message\" required></textarea>\n  <button type=\"submit\">Send Message</button>\n</form>\n\n\nRules:\n\ndata-form-name attribute is required.\naction must point to /_forms/{formName}.\nAll inputs must have name attributes.\nA submit button is required.\n\nCRITICAL: Remove Original Form Handlers\n\nIf the source site has JavaScript that handles form submissions, you MUST remove or replace it. Original site JS often does e.preventDefault() and shows a \"fake\" success toast — the data goes nowhere.\n\n// PROBLEM: This blocks real submissions!\nform.addEventListener('submit', (e) => {\n  e.preventDefault();\n  showToast('Message sent!');  // FAKE! Data not saved!\n});\n\n\nOption A (simplest): Remove the original JavaScript form handler entirely. The native <form action=\"/_forms/contact\" method=\"POST\"> will submit correctly.\n\nOption B (keep JS UX): Replace the handler with one that actually POSTs to FastMode:\n\nform.addEventListener('submit', async (e) => {\n  e.preventDefault();\n  const formName = form.dataset.formName;\n  const response = await fetch('/_forms/' + formName, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(Object.fromEntries(new FormData(form)))\n  });\n  if (response.ok) {\n    form.reset();\n    alert('Message sent!');  // NOW it's real!\n  }\n});\n\nDeployment & Build Status\nHow Deployment Works\nUpload: fastmode deploy site.zip reads the zip, validates it, and uploads it to the server.\nBuild: The server processes the package (renders templates, publishes pages). This happens asynchronously.\nWait: By default, deploy polls for build status every 3 seconds until the build completes or times out (default 2 minutes).\nResult: Success message with page count and version, or failure message with error details.\nDeploy Flags\nFlag\tDescription\n--force\tSkip the GitHub connection check. Use if the project has GitHub connected but you want to deploy via CLI anyway.\n--no-wait\tUpload only — don't wait for the build to finish. Useful for fire-and-forget.\n--timeout <ms>\tCustom build timeout in milliseconds. Default: 120000 (2 minutes).\nChecking Build Status\n\nAfter every deploy or content change, check the build status:\n\nfastmode status\n\n\nIf the build failed, status shows:\n\nThe error message\nBuild logs\nWhat went wrong\n\nAlways run fastmode status after deploying to verify the build succeeded.\n\nIf a build fails:\n\nRun fastmode status to see the error\nFix the issue (template errors, invalid tokens, missing files, etc.)\nRe-deploy with fastmode deploy site.zip\nDeploy History\nfastmode deploys                # Show last 10 deployments\nfastmode deploys --limit 5     # Show last 5\n\n\nShows status, version, duration, source, and errors for each deployment.\n\nExit Codes\ndeploy: Exits 1 if the build fails (when waiting).\nstatus: Exits 1 if the latest deploy shows \"Failed\".\nBoth: Exit 1 if no project is specified.\nValidation\n\nAlways validate before deploying. Validation catches errors that would cause build failures.\n\nValidation Workflow\n# 1. Validate the manifest\nfastmode validate manifest manifest.json\n\n# 2. Validate each template\nfastmode validate template pages/index.html -t static_page\nfastmode validate template templates/posts_index.html -t custom_index -c posts\nfastmode validate template templates/posts_detail.html -t custom_detail -c posts\n\n# 3. Validate the complete package\nfastmode validate package site.zip\n\nWhat Gets Checked\n\nManifest validation:\n\nValid JSON syntax\npages array exists and is not empty\nHomepage (path /) exists\nAll file paths are valid\ncmsTemplates format is correct (flat keys, not nested)\n\nTemplate validation:\n\nBalanced tags: {{#each}} has matching {{/each}}, {{#if}} has {{/if}}\nIndex templates have at least one {{#each}} loop\nDetail templates have CMS tokens\nRich text fields use triple braces {{{field}}}\nLoop variables only used inside loops\nAsset paths use /public/ prefix\nForms have required attributes\nYouTube iframes have referrerpolicy\n\nPackage validation:\n\nmanifest.json exists at root\nAll referenced files exist in the zip\nAssets are in public/ (not assets/ or root)\nTemplates are in templates/ (not collections/)\nAll templates pass individual validation\nValidation with Schema Check\n\nAdd -p to validate tokens against the actual project schema:\n\nfastmode validate template templates/posts_detail.html -t custom_detail -c posts -p \"My Project\"\n\n\nThis reports which tokens reference fields that don't exist in the schema yet, with instructions to create them via fastmode schema sync.\n\nCommon Mistakes & How to Fix Them\n1. Assets return 404\n\nProblem: CSS, JS, or images don't load. Cause: Files are in /assets/ instead of /public/, or paths don't include /public/. Fix: Move all static files to the public/ folder. Reference them as /public/css/style.css.\n\n2. Rich text shows as raw HTML\n\nProblem: Content displays <p>Hello</p> as text instead of rendering it. Cause: Using double braces {{body}} on a rich text field. Fix: Use triple braces {{{body}}} for all richText fields.\n\n3. Collection listing page is blank\n\nProblem: Index template shows no items. Cause: Missing {{#each collectionSlug}} loop. Fix: Add a loop: {{#each posts}}...{{/each}}.\n\n4. All detail pages look the same\n\nProblem: Every item page shows identical content. Cause: Detail template has no CMS tokens — just static HTML. Fix: Use tokens like {{name}}, {{{body}}}, {{image}} in the detail template.\n\n5. Manifest uses wrong format\n\nProblem: Build fails with manifest errors. Cause: Using nested objects or \"collections\" instead of flat \"cmsTemplates\" keys. Fix: Use flat format: \"postsIndex\", \"postsIndexPath\", \"postsDetail\", \"postsDetailPath\".\n\n6. Relation field is empty after create\n\nProblem: Created an item with a relation field but it's null. Cause: Used the item's name instead of its UUID. Fix: Run fastmode items relations <collection> --field <field> to get the UUID, then use that.\n\n7. Forms don't submit\n\nProblem: Form appears to submit (shows toast/alert) but no data is received. Cause: Original JavaScript calls preventDefault() and shows a fake success message. Fix: Remove any form JavaScript that blocks submission. Use data-form-name and action=\"/_forms/formName\".\n\n8. Static pages can't be edited in CMS\n\nProblem: Pages appear in the CMS but have no editable content. Cause: Missing data-edit-key attributes on text elements. Fix: Add data-edit-key=\"unique-key\" to every text element that should be editable.\n\n9. Deploy blocked by GitHub\n\nProblem: deploy refuses to upload, says GitHub is connected. Cause: Project has GitHub auto-deploy enabled. Fix: Use --force flag: fastmode deploy site.zip --force.\n\n10. Build fails after deploy\n\nProblem: Upload succeeds but build fails. Fix: Run fastmode status to see the error. Common causes: invalid tokens, missing template files, malformed manifest. Fix the issue and re-deploy.\n\n11. Template URLs don't match manifest\n\nProblem: Links in templates point to wrong paths. Cause: Template hardcodes /posts/ but manifest sets \"postsIndexPath\": \"/blog\". Fix: Make sure hardcoded links in templates match the paths defined in manifest.json.\n\n12. Loop variables undefined\n\nProblem: {{@index}} or {{@first}} shows nothing. Cause: Used outside of a {{#each}} block. Fix: Only use loop variables inside {{#each}}...{{/each}}.\n\n13. Duplicate SEO meta tags\n\nProblem: SEO tags show up twice in the rendered HTML. Cause: HTML templates include <title>, <meta name=\"description\">, or Open Graph tags. Fix: Remove all SEO tags from templates. FastMode manages them automatically via CMS Settings. See the SEO Tags section above.\n\n14. CSS background images and fonts broken\n\nProblem: Background images or custom fonts don't load. Cause: CSS files use relative paths like url('../images/bg.jpg') or url('../fonts/custom.woff'). Fix: Update all paths inside CSS files to use /public/ prefix: url('/public/images/bg.jpg'), url('/public/fonts/custom.woff2').\n\n15. Hardcoded example content instead of CMS tokens\n\nProblem: Index page shows static placeholder cards instead of real CMS data. Cause: Template has hardcoded HTML cards instead of {{#each}} loops with CMS tokens. Fix: Replace hardcoded content blocks with {{#each collection}}...{{/each}} loops using CMS field tokens.\n\nPre-Deployment Checklist\n\nRun through this checklist before every deploy:\n\nStructure:\n\n manifest.json at package root\n Static pages in pages/ folder\n CMS templates in templates/ folder\n ALL assets in public/ folder (not assets/)\n\nSEO (CRITICAL):\n\n NO <title> tags in HTML\n NO <meta name=\"description\"> tags\n NO <meta property=\"og:*\"> tags\n NO <link rel=\"icon\"> tags\n\nManifest:\n\n Homepage page with \"path\": \"/\" exists\n CMS templates use flat cmsTemplates keys (NOT nested, NOT collections)\n Paths match original site URLs\n\nTemplates:\n\n Index templates have {{#each}} loops\n Detail templates have CMS tokens ({{name}}, {{{body}}}, etc.)\n Rich text fields use triple braces {{{field}}}\n All {{#each}} have matching {{/each}}\n All {{#if}} have matching {{/if}}\n Static UI images use /public/ paths\n Content images use CMS tokens with {{#if}} wrappers\n\nStatic Pages:\n\n data-edit-key on every editable text element\n Keys are unique across the entire site\n Forms have data-form-name and action=\"/_forms/{name}\"\n Original form JavaScript handlers removed or replaced\n\nAssets:\n\n All HTML asset paths use /public/ prefix\n CSS background-image and font url() paths use /public/ prefix\n External URLs (Google Fonts, CDNs) unchanged\n\nValidation:\n\nfastmode validate manifest manifest.json\nfastmode validate template <each-template> -t <type> [-c <collection>]\nfastmode validate package site.zip\n\nError Handling & Exit Codes\n\nAll commands exit with code 0 on success and code 1 on failure.\n\nCommands that exit 1\nScenario\tCommands\nNo project specified\tAll project-scoped commands\nFile not found\tschema sync, items create -f, validate *\nInvalid JSON\tschema sync, items create -d, items update -d\nValidation errors\tvalidate manifest, validate template, validate package\nBuild failed\tdeploy (when waiting), status\nDelete without --confirm\titems delete\nError Messages\nError: No project specified.\nUse -p <id-or-name>, set FASTMODE_PROJECT env var, or run: fastmode use <project>\n\nError: File not found: schema.json\n\nError: Invalid JSON in --data argument\n\nError: Deletion requires the --confirm flag. This action cannot be undone.\n\nAuthentication Errors\n\nMost commands auto-trigger fastmode login if credentials are missing or expired. If authentication fails:\n\nRun fastmode login manually\nComplete the browser flow\nRetry the command\nFile Locations\nPath\tPurpose\n~/.fastmode/credentials.json\tOAuth tokens (auto-created by login)\n~/.fastmode/config.json\tDefault project setting (created by use)\n\nBoth files have restricted permissions (0o600 — owner read/write only).\n\nNotes\nAll project-scoped commands use your default project (set with fastmode use). Override with -p <name-or-id>.\nItem data (-d) must be valid JSON. For complex data, write a file and use -f data.json.\nRich text fields accept HTML content (e.g. <p>, <h2>, <ul>, <a>). Always use triple braces in templates.\nRelation fields require item IDs (UUIDs). Use fastmode items relations to find available IDs.\nThe --draft flag creates unpublished items. Use --publish/--unpublish to change status.\nEvery site gets free hosting, free SSL, and a .fastmode.ai subdomain. Custom domains can be configured.\nAfter deploying or making content changes, always run fastmode status to verify the build succeeded.\nUse fastmode examples <type> and fastmode guide [section] for built-in documentation and code snippets.\nPackage Provenance\nnpm package: fastmode-cli\nSource code: github.com/arihgoldstein/fastmode-mcp\nWebsite: fastmode.ai\nAuthor: Arih Goldstein\nLicense: MIT"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/arihgoldstein/fastmode",
    "publisherUrl": "https://clawhub.ai/arihgoldstein/fastmode",
    "owner": "arihgoldstein",
    "version": "1.5.3",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/fastmode",
    "downloadUrl": "https://openagent3.xyz/downloads/fastmode",
    "agentUrl": "https://openagent3.xyz/skills/fastmode/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fastmode/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fastmode/agent.md"
  }
}