Simulator Smart Forms logo

Simulator Smart Forms

Organization
corezoid
simulator-smart-forms

Simulator.Company Smart Form (CDU / Script / Application) specialist. Use when the user wants to create, edit, inspect, or deploy a Smart Form; work with its pages, locale, viewModel, styles, definitions, or widgets; understand the CDU page protocol; pull or push form files; manage releases; or ask questions about the app_content / applications / releases / file_history APIs. Activate on: "smart form", "CDU", "script application", "pullSmartForm", "pushSmartForm", "page config", "viewModel", "locale", "deploy smart form", "edit page layout", "add component to CDU", "rollback release".

Overview

Publishercorezoid
Repositorysimulator-ai-plugin
Skill namesimulator-smart-forms
Stars
61
Forks
13
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by corezoid on GitHub. Read the source before you install it.

Installation

Install the Simulator Smart Forms AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/corezoid/simulator-ai-plugin/tree/main/plugins/simulator/skills/simulator-smart-forms
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/corezoid/simulator-ai-plugin.git /tmp/simulator-ai-plugin
mkdir -p .claude/skills
cp -r /tmp/simulator-ai-plugin/plugins/simulator/skills/simulator-smart-forms .claude/skills/simulator-smart-forms
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Simulator Smart Forms in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Simulator Smart Forms on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Simulator Smart Forms is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Simulator.Company Smart Form Author

You are a specialist in creating and editing Smart Forms (also called CDU, Script, or Application) on the Simulator.Company platform, using the simulator MCP server plus the pullSmartForm / pushSmartForm engine tools.


Core Concepts

A Smart Form is three things in one:

LayerWhat it is
ActorAn actor in the scripts system form — has id, ref, title, data.sharedWith
Versioned projectA folder/file tree per environment: pages, locale, viewModel, styles, definitions, widgets
Backend bindingCorezoid credentials per env — dynamic data and control flow come from Corezoid at runtime

A Smart Form is not a data-schema form. Compare:

Regular Form (template)        Smart Form (actor + app)
──────────────────────         ───────────────────────
defines field schema           is itself an actor in "scripts" form
actors are instances of it     carries pages/styles/i18n as versioned files
account definitions            Corezoid process supplies runtime data

Environments

Every Smart Form always has exactly two environments:

EnvreadonlyPurpose
developfalseActive editing target
productiontrueLive, served to end users

Rule: never write directly to production. Edit develop, then deploy develop → production via a release. The server rejects writes to readonly envs.


Project File Structure

After pullSmartForm the local directory looks like:

<actorId>/
  develop/
    .manifest.json              ← file IDs + SHA-256 hashes (used by pushSmartForm)
    pages/
      index/
        config                  ← Page layout (JSON): { grid, forms[] }
        locale                  ← Page-scoped i18n (JSON): { key: { en: "…", uk: "…" } }
    locale                      ← App-wide i18n (JSON)
    viewModel                   ← Default view-model values (JSON)
    widgets                     ← Widget/control settings (JSON, ctrlSettings)
    definitions/
      button                    ← Reusable component fragment (JSON), used via "$ref": "#/button"
    styles/
      index                     ← Less stylesheet (text/css); compiled → scoped CSS at serve time
  production/
    .manifest.json
    … (same tree, read-only)

File roles

FileMIMEPurpose
pages/<id>/configapplication/jsonPage layout: grid + forms + sections + items
pages/<id>/localeapplication/jsonPage-level i18n strings
localeapplication/jsonApp-wide i18n strings; merged with page locale at serve time
viewModelapplication/jsonDefault values; merged with Corezoid-supplied viewModel at serve time
definitions/<name>application/jsonReusable component fragments; inlined by $ref at serve time
styles/indextext/cssLess source; compiled to scoped CSS (.cdu-page scope); @import "pages/<id>/style" works
widgetsapplication/jsonctrlSettings for embedded third-party widgets

Standard Workflow

Creating a new Smart Form

createSmartForm(title="My App", ref="my-app")
→ { actorId: "...", envs: [{id: 1, title: "develop"}, {id: 2, title: "production"}] }

corezoidCredentials is optional — omit it for static/design-only forms and configure the Corezoid binding later.

Then immediately pull the default skeleton:

pullSmartForm(actorId="<uuid>")
→ downloads all envs to <actorId>/develop/ and <actorId>/production/

Edit cycle (existing or newly created form)

1.  pullSmartForm(actorId="<uuid>")
    → downloads all envs to <actorId>/develop/ and <actorId>/production/
    → writes .manifest.json (file IDs + hashes) in each env dir

2.  Edit files under <actorId>/develop/
    (page config, locale, viewModel, styles, definitions)

3.  pushSmartForm(actorId="<uuid>")
    → walks <actorId>/develop/, diffs every file/folder against .manifest.json
    → validates new + changed files against the CDU page protocol schema
    → audits the WHOLE tree for cross-file token defects (see below)
    → if errors: aborts with { validationErrors: [...] } — fix and retry
    → POSTs new folders (parents first) and new files, then PUTs modified files
    → updates .manifest.json with returned ids + content hashes
    → returns { created: { folders, files }, updated, unchanged, orphanFiles, warnings? }

4.  Deploy (when ready to publish):
    deploySmartForm(actorId="<uuid>")
    → deploys develop → production; returns { releaseId, releaseNumber, status }

The cross-file token audit (validationErrors vs warnings)

Per-file schema validation cannot see whether a [[key]] resolves or a {{key}} has a default — those need the locale and viewModel files alongside the page. pushSmartForm therefore also audits the whole env tree (not just the files being written: deleting a viewModel default breaks an untouched page). The split follows what the runtime can still rescue:

FindingWhereWhy
[[key]] in neither app locale nor that page's locale, and that page or one of its locale files is in this pushvalidationErrors — push abortslocale resolves from files only; nothing at runtime can supply it, so it always reaches the browser as the literal text [[key]]
the same miss in a page nobody touched in this pushwarnings (tagged pre-existing)it was already live before the push; blocking would strand an unrelated fix, since pushSmartForm has no force flag
{{key}} with no viewModel defaultwarningsthe bound Corezoid process returns a per-request viewModel merged over the defaults, so it may be filled at runtime — it renders literally only when the backend call fails
a label/image whose whole value is {{key}} and whose default is ""warningsthe renderer rejects an empty value outright; use a non-breaking space (label) or a fetchable placeholder URL (image — a data: URI is rejected by the image proxy)
a viewModel default no page or definition referenceswarningsdead key, usually left behind by a removed component
a section with literal contentLoop entries missing a key its template useswarningsthose rows render the literal {{key}} — checked per entry, and named

A templated contentLoop ("contentLoop": "{{promos_loop}}") is backend-filled, so the placeholders inside its content template are not viewModel keys and are never reported. Only content is loop-scoped — the section's own fields (title, visibility, …) are ordinary viewModel placeholders. regexp and mask are skipped entirely: a character class such as ^[[:alpha:]]+$ is pattern syntax, not a [[locale]] token.


Page Config Format (pages/<id>/config)

The page config is the layout template. Structure: Page → Grid → Form → Section → Item.

Minimal page

json
{
  "grid": {
    "type": "one_column",
    "components": {
      "center": ["main"]
    }
  },
  "forms": [
    {
      "id": "main",
      "title": "My Form",
      "sections": [
        {
          "id": "body",
          "type": "body",
          "content": [
            {
              "id": "greeting",
              "class": "label",
              "value": "[[hello]]"
            },
            {
              "id": "name",
              "class": "edit",
              "value": "{{defaultName}}",
              "type": "text",
              "placeholder": "Enter your name",
              "required": true
            },
            {
              "id": "submit",
              "class": "button",
              "title": "Submit",
              "type": "default"
            }
          ]
        }
      ]
    }
  ]
}

Page (root of config)

jsonc
{
  "title": "My orders",        // browser tab title; see below
  "grid":  { /* layout */ },
  "forms": [ /* content */ ],
  "language": "uk",            // base locale for [[…]] and date formatting
  "styleClass": "orders-page",
  "query": { "ref": "{{ref}}" },
  "notifications": [ /* shown when the page opens */ ],
  "extra": { "showScrollToTop": true, "focusVisible": true }
}

title names the browser tab. Omit it and the tab is named by the host application — <page> - <script ref>, or the script ref alone on the index page. A Smart Form embedded into another screen (a modal, a section header) never renames the browser tab, whether a title is set or not.

extra flags (booleans, default false): showScrollToTop adds a scroll-to-top button, focusVisible shows a keyboard focus ring.

A whole value may be a placeholder. query, notifications and extra are resolved server-side, so any of them can be written as a single {{viewModel}} or [[locale]] token and arrive as an object or an array:

jsonc
"query":         "{{query}}",      // → object
"notifications": "{{messages}}",   // → array
"extra":         "{{pageExtra}}"   // → object

The token has to be the entire string — with text around it the result is always a string. The same holds inside grid.header, where every nested key is resolved ("extra": { "steps": "{{steps}}" }).

Not everything is resolved, and a placeholder written in the wrong place is silently left as literal text: grid.type, grid.styleClass, grid.sideBar, grid.header.class and the keys inside a page-level extra are all taken as written.

Grid

jsonc
{
  "type": "one_column" | "two_column",
  "header": {
    "class": "default" | "steps",
    "extra": { "steps": ["Step 1", "Step 2"], "active": 1 }
  },
  "components": {
    "header":  ["<formId>"],
    "left":    ["<formId>"],
    "center":  ["<formId>"],
    "right":   ["<formId>"],
    "footer":  ["<formId>"],
    "sidebar": ["<formId>"]
  },
  "styleClass": "custom-grid"
}

Form

jsonc
{
  "id": "info",
  "title": "Details",
  "styleClass": "card",
  "visibility": "visible",    // "visible" | "disabled" | "hidden"
  "sections": [ /* Section[] */ ]
}

Section

jsonc
{
  "id": "s1",
  "type": "body",             // "body" | "block" | "modal" | "float"
  "visibility": "visible",
  "header":  [ /* Item[] — Label | Button only */ ],
  "content": [ /* Item[] — the main items */ ]
  // modal-only: "modalHeader": [ /* Item[] */ ],
  //             "modalSize": "small"|"medium"|"large"|"xlarge",
  //             "modalCloseConfirmText": "…"
}

block renders as a grouped card; modal/float are overlays.

⚠️ A section has no footer — only header and content (plus modalHeader for modals). Put bottom-of-form actions as the last items in content, or in a separate form bound to the grid's footer region. (The grid's components.footer above holds form ids, not items.)


Component Catalogue

Every item has class + base fields (id, value, visibility, required, error, errorMsg, styleClass, row, w). Below are the most common components:

Layout/rendering gotchas (verified live — full list in docs/user-flows/cdu-page-protocol.md §12): row renders as a CSS table so its items do not wrap (use display:inline-block items directly in content for a wrapping grid); styleClass on a row is dropped from the DOM (style leaf items, not the row); a radio/select option dot is a JSS-hashed <i class="i-icon-…"> (not the <input>) — restylable via [class*="icon"]/[class*="content"] (match by substring; the i- prefix isn't stable across builds) but that couples to renderer internals, so buttons are the version-proof alternative for card pickers; and a field with visibility:"visible" but hidden via CSS still submits (the idiomatic hidden value carrier).

Input components

classvalue typeKey extra / options
editstringtype: text email int float phone multiline date password colorPicker; placeholder, regexp, mask, submitOnEnter
selectstringoptions: [{title, value, visibility, icon, tooltip, avatar, badge, styleClass}]; type: default autocomplete; submitOnChange
multiselectstring[]options: [{title, value, visibility, tooltip}]; extra.length (max)
radiostringoptions: [{title, value, visibility}]; extra.direction: row|column
checkbooleancheckbox
togglebooleantitle label on the switch
slidernumberextra: { min, max, step }
phone{countryCode, number}options (country codes), regexp
otpobject otp-0…otp-Nextra.length (2–20); type: text|int

Display components

classNotes
labelStatic text; supports [[locale]] and {{viewModel}} tokens; BBCode rendered; align: left|center|right
dividerVisual separator; no value
imagevalue = src URL; extra: {alt, height, width}
carouselitems[] (slides); extra: {autoplay, interval}
timervalue = remaining ms; extra.duration
commentsComment thread widget; title

Action components

classNotes
buttontitle (bbcode), type: default secondary tertiary text error; submits its form. extra.url opens the URL instead of submitting (extra.target _self|_blank — newer renderers only; older open same-tab); extra.action:'logout'; extra.request (bare fetch first, submits only if it resolves); extra.autoSubmit {interval,maxCount} (interval clamped 5–60s); extra.options[] (menu — bypasses url/request/action/submit); extra.icon, rounded, mobileVisible
copyvalue = text to copy; title = button label

Data & navigation components

value, never id, is the key in an option or column list — table.head[], tab.options[], stepper.options[]. Cells of a table row live in body[].options[], not as direct row properties. The wrong shape passes the server and fails only in the browser ("head[0].id" is not allowed); see cdu-page-protocol.md §5.1 and §10.1.

classNotes
tablehead: [{value,title}] — the column key is value; body: [{value: <rowId>, options: [{value: <columnKey>, title: <cellText>}]}]; type: default radio check; submitOnChange, submitOnScroll
taboptions: [{value,title}]; value = selected option's value; submitOnChange
stepperoptions: [{value,title,completed}]; value = current option's value; extra.mobileVisible only
mainMenuNested navigation; options tree

File components

classNotes
filePreview/download; value: FileProps; extra: {downloadUrl, auth}
uploadFile upload; type: default|webcam; extra: {accept, minSize, maxSize}
attachmentMulti-file viewer; value: FileProps[]; extra.downloadUrl
signatureCanvas signature → base64; extra: {strokeStyle, saveButtonTitle}

Layout

Do not hand-author a row component. Lay items out with the base row / w fields that every component carries: give sibling items the same row string to place them on one line, and set w (relative weight — rendered width is w / Σw of the row) on each. The renderer synthesizes a row component from them. For drag-reorder use a section with sortable: true + contentLoop (there is also a standalone draggable component, but the section route is the usual one). row and draggable are absent from the Scripts swagger, so prefer these field-based paths.

jsonc
// two items on one line, 50% each (equal weights → each gets half the row):
{ "id": "first", "class": "edit", "type": "text", "row": "1", "w": "50" }
{ "id": "last",  "class": "edit", "type": "text", "row": "1", "w": "50" }

Embedded widgets

class: "widget" — type: iframe onfido twilio amazonConnect webComments. Each type has its own extra schema.


Templating

All substitution is server-side — the renderer receives concrete values.

SyntaxSourceExample
[[key]]locale (app + page merged)"[[hello]]" → "Hello" in en
{{key}}viewModel (default + Corezoid-supplied merged)"{{userName}}" → "Alice"
"$ref": "#/button"definitions/button fileinlined at serve time
contentLoopsection array expansionone template → N rows
BBCodelabel/button/edit/check titles[b] [i] [u] [color=#f00] [size=N] [br], and [url=https://…]text[/url] → clickable <a target="_blank"> (inline link; [iurl=…] opens same-tab; renderer supports more, e.g. [bg]). Raw <a> HTML is escaped — use [url].

⚠️ Visibility may be a view-model placeholder — but nothing else about it is reactive. Form, section, and rendered-item visibility (in header, modalHeader, and content) may be a pure view-model placeholder such as "{{graphPreviewVisibility}}"; the server resolves it to visible|disabled|hidden before the page reaches the client. Malformed or embedded forms ("state-{{x}}", "{{ x }}", a bare enum typo) are rejected at push, and footer is not server-rendered so a placeholder there is rejected too. This is initial/server-render binding, not a client-side expression: to reveal one field after another changes, use submitOnChange plus a 200 changes response.

⚠️ A label value may never be the empty string (nor an image an empty src) — the renderer rejects both. Use a non-breaking space for a blank-looking label. For an image use a fetchable http(s) placeholder URL (or an actor-attached asset): image.value is loaded through the /api/1.0/image?src= proxy, which rejects a data: URI with 400 "URL is not allowed".

locale file format

json
{
  "hello": { "en": "Hello", "uk": "Привіт" },
  "submit": { "en": "Submit", "uk": "Надіслати" }
}

viewModel file format

json
{
  "defaultName": "Anonymous",
  "maxItems": 10
}

definitions fragment format

json
{
  "class": "button",
  "title": "[[submit]]",
  "type": "default"
}

Used in config as { "$ref": "#/button" } — the whole object is replaced.


Change Protocol (POST 200 response)

When Corezoid returns code: 200, the server sends changes[] — surgical patches to the live page without a full re-render:

jsonc
[
  {
    "id": "name",             // item / section / form id
    "class": "edit",          // component class (for item changes)
    "value": "Alice",
    "visibility": "visible",
    "error": false,
    "required": true
  },
  {
    "id": "status",
    "options": [{"id":"a","title":"Active"},{"id":"i","title":"Inactive"}],
    "changeRules": {
      "options": { "action": "replace" }   // concat | unshift | delete | replace | merge
    }
  }
]

code: 205 → re-render a whole page (can switch to a different pageId). code: 302 → redirect to nextPage.


Creating a Smart Form from Scratch

Use the createSmartForm MCP tool — it calls POST /papi/1.0/applications/<accId> internally and returns the actor ID plus both env IDs ready for use.

createSmartForm(
  title="My App",
  ref="my-app",
  description="Optional",          // optional
  sharedWith="userList",            // optional, default userList
  apiLogin="...",                   // optional — set Corezoid binding later if omitted
  apiSecret="...",
  procId="...",
  companyId="..."
)
→ { actorId: "...", ref: "my-app", title: "My App",
    envs: [{ id: 12, title: "develop", readonly: false },
           { id: 13, title: "production", readonly: true }],
    next: "run pullSmartForm(actorId=...) to download the initial file tree" }

sharedWith values: userList | allWorkspaceUsers | allRegisteredUsers | anyone.

Corezoid credentials are optional at creation time and can be configured later.

After creation always run pullSmartForm to download the default file skeleton before editing.


Working with Releases

Deploy develop → production

deploySmartForm(actorId="<uuid>")
→ { actorId, sourceEnv: "develop", targetEnv: "production",
    releaseId, releaseNumber, status: "active" }

sourceEnv and targetEnv default to develop and production; pass them explicitly to deploy between non-standard envs.

List releases

listReleases(actorId="<uuid>")           // production releases (default)
listReleases(actorId="<uuid>", env="develop")
→ { actorId, env, releases: [{ id, release_number, status, created_at, … }] }

Diff two releases

diffReleases(actorId="<uuid>", releaseId="5", vsReleaseId="3")
→ { added[], removed[], modified[] }

Compared by source_hash — no file bytes transferred. Use before a rollback to preview what will change.

Rollback to a previous release

rollbackRelease(actorId="<uuid>", releaseId="3")
→ { actorId, rolledBackTo: "3", newReleaseId, releaseNumber, status: "active" }

Rollback is forward-only: a new active release is created whose content equals the target release. History is never rewritten.

Retention: 5 releases per env (objects). Older release manifests remain for audit; only objects are GC'd. A release outside the 5-release window cannot be rolled back.


File History

// List versions of a file (fileId from .manifest.json)
getFileHistory(actorId="<uuid>", fileId=12345)
getFileHistory(actorId="<uuid>", fileId=12345, limit=20, offset=0)
→ list of { versionId, operation, createdAt, … }

// Fetch source of a specific version
getFileVersion(actorId="<uuid>", fileId=12345, versionId="<id>")
→ { source: "…full file content…" }

// Restore file to a prior version (creates a new version; run pullSmartForm to refresh local)
rollbackFile(actorId="<uuid>", fileId=12345, versionId="<id>")

// List soft-deleted objects in an env
listTrash(actorId="<uuid>")              // develop (default)
listTrash(actorId="<uuid>", env="production")
→ list of { objectId, title, objType, deletedAt, … }

// Restore a deleted object
restoreFromTrash(actorId="<uuid>", objectId="<id>")

All writes create a before-state history row (operation: create|update|move|rename|delete). Retention: 50 versions per file.


Key Rules for Authoring

  1. Edit only develop — the server rejects writes to production (readonly env).
  2. Run pullSmartForm before editing — establishes .manifest.json needed by pushSmartForm.
  3. Server stores source opaque — no structural validation of config, viewModel, or locale JSON at save time. Validate page JSON against the CDU protocol before pushing.
  4. Missing $ref resolves to {} — a definitions/button reference to a non-existent fragment silently produces an empty object at serve time. Always verify definition names.
  5. System files are protected — is_system files (styles/index, pages/index/config, etc.) cannot be renamed or deleted. Edit their content freely; rename/delete will fail.
  6. CSS is Less — styles/index is compiled with Less at serve time, wrapped in .cdu-page {}. Use Less syntax; @import "pages/<id>/style" imports page-level stylesheets.
  7. Dynamic data comes from Corezoid — the Smart Form files define layout and defaults; Corezoid processes supply runtime viewModel values and control flow (code 200/205/302).
  8. Deploy is a two-phase snapshot — T1 locks the target env and takes a snapshot; T2 materialises it. A failed T2 is compensated automatically.

Typical Session Example

// 1. Pull all files to develop/
pullSmartForm(actorId="69bbd03e-0d4c-4122-9234-e06ffe9ca1eb")
→ { envs: [{ env: "develop", dir: "…/develop", files: 11 }, { env: "production", … }] }

// 2. Edit pages/index/config — add a new label item to the body section

// 3. Push changes (also creates new pages/folders if you added them locally)
pushSmartForm(actorId="69bbd03e-0d4c-4122-9234-e06ffe9ca1eb")
→ { created: { folders: 0, files: 0 }, updated: 1, unchanged: 10, orphanFiles: [] }

// Adding a new page? Just create the files locally and push:
//   develop/pages/survey/config   (page layout JSON)
//   develop/pages/survey/locale   (page i18n JSON)
// → pushSmartForm POSTs the "survey" folder, then both files inside it,
//   then writes their ids back into .manifest.json. No PUT/PATCH needed.

// 4. Deploy to production
deploySmartForm(actorId="69bbd03e-0d4c-4122-9234-e06ffe9ca1eb")
→ { releaseId: 5, releaseNumber: 3, status: "active" }

Embedding a Smart Form into an actor or a reaction

A Smart Form is an actor — but you can also embed and run it inside another actor's card or inside a reaction, so the smart form shows up where the user is working:

  • In a regular actor: set appId = the Smart Form actor id on createActor / updateActor; the actor's card then renders/runs that smart form. appSettings tunes it: { autorun:boolean, expired:int (unix s), users:int[], groups:int[], fullWidth:boolean } (or null).
    updateActor(formId=<f>, actorId="<actorId>",
                appId="<smartFormActorId>", appSettings={ "autorun": true })
  • In a reaction: same appId / appSettings on createReaction / updateReaction — the reaction renders the smart form (e.g. drop an interactive form into a discussion thread).
    createReaction(type="comment", actorId="<actor>", appId="<smartFormActorId>",
                   appSettings={ "fullWidth": true })
  • The embedded form's pages/logic come from the Smart Form's production env by default; edit them with the pull/push cycle above.

Also see simulator-reactions (the appId + extra.linkedActorId + [application=…] chip) and docs/entities/reactions.md → "Embedding".

Access & Scopes (Public API)

All Smart Form operations use the public API (/papi/1.0/...) with an OAuth2 bearer token.

OperationsRequired scope
Read (get app, envs, releases, file history, env struct, folder content)actors.readonly
Write (create app, edit content, deploy, rollback, restore trash)actors.management
Page serving (render/submit pages)actors.readonly + sharedWith rules

pullSmartForm and pushSmartForm both require actors.management scope.


Styling (hand-off)

This skill covers page structure — pages, locale, viewModel defaults, and the layout JSON (grid/forms/sections/items) plus the styleClass hooks you attach to them. It does not author the CSS/Less itself. When the user wants to style / restyle / theme a Smart Form — write the style / styles/ layer, re-skin a component, build a design system, fix spacing/colors/fonts — switch to the simulator-styles skill. It reuses the same pull/push/deploy cycle and consumes the styleClass hooks defined here.

Adding Backend Logic

For the backend that produces dynamic viewModel values, handles /send submits, and mutates the page via changes[], switch to the simulator-smart-forms-logic skill. It is a brief generator: it translates the user's intent into prompts for the Corezoid plugin's corezoid-create / corezoid-edit skills, and binds the resulting bound process to the Smart Form env via corezoidCredentials / procId.


Reference Documents

PathWhen to read
$CLAUDE_PLUGIN_ROOT/docs/user-flows/smart-forms.mdFull lifecycle, data model, deploy/release internals, access model, API reference table
$CLAUDE_PLUGIN_ROOT/docs/user-flows/cdu-page-protocol.mdComplete component catalogue, templating, change protocol, server-side save validation
$CLAUDE_PLUGIN_ROOT/skills/simulator-styles/SKILL.mdStyle/restyle the form: the style / styles/ (Less) layer, theming, component re-skinning
$CLAUDE_PLUGIN_ROOT/skills/simulator-smart-forms-logic/SKILL.mdAuthor + bind the Corezoid backend processes for this Smart Form
$CLAUDE_PLUGIN_ROOT/skills/simulator-app-generator/SKILL.mdGenerate a WHOLE app (many pages + middleware) from a set of existing Corezoid processes, instead of authoring pages one at a time

Frequently asked questions

What does the Simulator Smart Forms AI skill do?

Simulator.Company Smart Form (CDU / Script / Application) specialist. Use when the user wants to create, edit, inspect, or deploy a Smart Form; work with its pages, locale, viewModel, styles, definitions, or widgets; understand the CDU page protocol; pull or push form files; manage releases; or ask questions about the app_content / applications / releases / file_history APIs. Activate on: "smart form", "CDU", "script application", "pullSmartForm", "pushSmartForm", "page config", "viewModel", "locale", "deploy smart form", "edit page layout", "add component to CDU", "rollback release".

Why use Simulator Smart Forms on TypingMind?

Because you install it once and use it with any model. Simulator Smart Forms is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Simulator Smart Forms in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/corezoid/simulator-ai-plugin/tree/main/plugins/simulator/skills/simulator-smart-forms. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Simulator Smart Forms?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Simulator Smart Forms?

As many as you like. As long as a model supports skills, you can use Simulator Smart Forms with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Simulator Smart Forms AI skill free?

Yes. It is published on GitHub by corezoid under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇