diff --git a/.claude/rules/landing-seo-geo.md b/.claude/rules/landing-seo-geo.md index 3f69b3993ef..4f6ec536ad0 100644 --- a/.claude/rules/landing-seo-geo.md +++ b/.claude/rules/landing-seo-geo.md @@ -22,7 +22,7 @@ paths: - **Answer-first pattern**: each section's H2 + subtitle should directly answer a user question (e.g. "What is Sim?", "How fast can I deploy?"). - **Atomic answer blocks**: each feature / template card should be independently extractable by an AI summariser. - **Entity consistency**: always write "Sim" by name — never "the platform" or "our tool". -- **Keyword density**: first 150 visible chars of Hero must name "Sim", "AI workspace", "AI agents". +- **Keyword density**: first 150 visible chars of Hero must name "AI workspace" and "AI agents". "Sim" is carried by the title tag, the meta description, and the Hero `sr-only` summary — the H1 does not have to spend its opening words on the brand. - **sr-only summaries**: Hero and Templates each have a `

` (~50 words) as an atomic product/catalog summary for AI citation. - **Specific numbers**: prefer concrete figures ("1,000+ integrations", "15+ AI providers") over vague claims. diff --git a/apps/docs/content/docs/en/api-reference/(generated)/workflows/meta.json b/apps/docs/content/docs/en/api-reference/(generated)/workflows/meta.json index 491129e3cdf..8e2caa1abe8 100644 --- a/apps/docs/content/docs/en/api-reference/(generated)/workflows/meta.json +++ b/apps/docs/content/docs/en/api-reference/(generated)/workflows/meta.json @@ -5,6 +5,8 @@ "cancelExecution", "listWorkflows", "getWorkflow", + "exportWorkflow", + "importWorkflow", "deployWorkflow", "undeployWorkflow", "rollbackWorkflow", diff --git a/apps/docs/openapi.json b/apps/docs/openapi.json index 49d6e7ed91b..1db7ab8e662 100644 --- a/apps/docs/openapi.json +++ b/apps/docs/openapi.json @@ -1035,6 +1035,160 @@ } } }, + "/api/v1/workflows/import": { + "post": { + "operationId": "importWorkflow", + "summary": "Import Workflow", + "description": "Create a new workflow in a workspace from an export payload produced by GET /api/v1/workflows/{id}/export. Block, edge, loop and parallel identifiers are regenerated, so the same payload can be imported repeatedly and alongside its source workflow. The workflow name is de-duplicated against the target folder. Requires write permission on the target workspace.", + "tags": ["Workflows"], + "x-codeSamples": [ + { + "id": "curl", + "label": "cURL", + "lang": "bash", + "source": "curl -X POST \\\n \"https://www.sim.ai/api/v1/workflows/import\" \\\n -H \"X-API-Key: YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"workspaceId\": \"a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64\", \"workflow\": {\"version\": \"1.0\", \"state\": {\"blocks\": {}, \"edges\": []}}}'" + } + ], + "requestBody": { + "required": true, + "description": "The target workspace and the workflow payload to import.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["workspaceId", "workflow"], + "properties": { + "workspaceId": { + "type": "string", + "minLength": 1, + "description": "The workspace to import the workflow into. Requires write permission.", + "example": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64" + }, + "folderId": { + "type": "string", + "minLength": 1, + "description": "Optional folder to place the imported workflow in. Defaults to the workspace root.", + "example": "8a4c2e6b-0d1f-4b3a-9c5e-7f2d8b4a6c91" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "description": "Optional override for the imported workflow's name. Defaults to the name carried in the payload, then to \"Imported Workflow\".", + "example": "Customer Support Agent (copy)" + }, + "description": { + "type": "string", + "maxLength": 2000, + "description": "Optional override for the imported workflow's description. Defaults to the description carried in the payload.", + "example": "Imported from the staging workspace" + }, + "workflow": { + "description": "The workflow to import. Accepts the export envelope returned by GET /api/v1/workflows/{id}/export, a bare workflow state object (`{ blocks, edges, ... }`), or a JSON string of either.", + "anyOf": [ + { + "$ref": "#/components/schemas/WorkflowExport" + }, + { + "type": "object", + "additionalProperties": true, + "minProperties": 1, + "description": "A bare workflow state object." + }, + { + "type": "string", + "minLength": 1, + "description": "A JSON string of either accepted object form." + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "The workflow was imported.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "description": "The newly created workflow.", + "$ref": "#/components/schemas/ImportedWorkflow" + }, + "limits": { + "$ref": "#/components/schemas/Limits", + "description": "Rate limit and usage information for the current API key." + } + } + }, + "example": { + "data": { + "id": "7d2e9f14-3a6b-4c8d-b1e5-9f0a2c7d4e83", + "name": "Customer Support Agent (1)", + "description": "Routes incoming support tickets", + "workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", + "folderId": null, + "createdAt": "2026-06-20T14:15:22Z", + "updatedAt": "2026-06-20T14:15:22Z" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "413": { + "description": "The request body exceeds the 10 MB import limit.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Request body exceeds the maximum allowed size" + } + } + } + } + } + }, + "423": { + "description": "The target folder is locked and cannot accept new workflows.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Folder is locked" + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + } + }, "/api/v1/workflows/{id}": { "get": { "operationId": "getWorkflow", @@ -1104,6 +1258,92 @@ } } }, + "/api/v1/workflows/{id}/export": { + "get": { + "operationId": "exportWorkflow", + "summary": "Export Workflow", + "description": "Export a workflow as a portable JSON envelope that POST /api/v1/workflows/import accepts verbatim. Credential and password field values stored on blocks are redacted from the payload, while `{{ENV_VAR}}` references and block positions are preserved. Workflow variables are returned as stored \u2014 they are plaintext configuration readable by anyone with workspace read, and secrets belong in environment variables, which travel as unresolved references. Requires read permission on the workflow's workspace. Returns 404 when the workflow does not exist or you do not have access to it. Workspace-scoped bindings (knowledge bases, workspace files, channels, projects, folders, MCP servers) are cleared, since those ids do not resolve in another workspace \u2014 an imported copy needs them re-selected.", + "tags": ["Workflows"], + "x-codeSamples": [ + { + "id": "curl", + "label": "cURL", + "lang": "bash", + "source": "curl -X GET \\\n \"https://www.sim.ai/api/v1/workflows/{id}/export\" \\\n -H \"X-API-Key: YOUR_API_KEY\"" + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The unique workflow identifier.", + "schema": { + "type": "string", + "example": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36" + } + } + ], + "responses": { + "200": { + "description": "The workflow export envelope.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "description": "The workflow export envelope.", + "$ref": "#/components/schemas/WorkflowExport" + }, + "limits": { + "$ref": "#/components/schemas/Limits", + "description": "Rate limit and usage information for the current API key." + } + } + }, + "example": { + "data": { + "version": "1.0", + "exportedAt": "2026-06-20T14:15:22Z", + "workflow": { + "id": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36", + "name": "Customer Support Agent", + "description": "Routes incoming support tickets", + "workspaceId": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64", + "folderId": null + }, + "state": { + "blocks": {}, + "edges": [], + "loops": {}, + "parallels": {}, + "metadata": { + "name": "Customer Support Agent", + "description": "Routes incoming support tickets", + "exportedAt": "2026-06-20T14:15:22Z" + } + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + } + }, "/api/v1/workflows/{id}/deploy": { "post": { "operationId": "deployWorkflow", @@ -7299,6 +7539,154 @@ } } } + }, + "WorkflowExportState": { + "type": "object", + "description": "The workflow graph. Block, edge, loop and parallel identifiers are regenerated on import, so a payload can be imported repeatedly without colliding with its source workflow.", + "properties": { + "blocks": { + "type": "object", + "description": "Map of block id to block definition, including canvas position and sub-block values.", + "additionalProperties": true, + "example": {} + }, + "edges": { + "type": "array", + "description": "Connections between blocks.", + "items": { + "type": "object", + "additionalProperties": true + }, + "example": [] + }, + "loops": { + "type": "object", + "description": "Loop container definitions, keyed by loop id.", + "additionalProperties": true, + "example": {} + }, + "parallels": { + "type": "object", + "description": "Parallel container definitions, keyed by parallel id.", + "additionalProperties": true, + "example": {} + }, + "metadata": { + "type": "object", + "description": "The source workflow's name, description and export timestamp.", + "properties": { + "name": { + "type": "string", + "example": "Customer Support Agent" + }, + "description": { + "type": "string", + "example": "Routes incoming support tickets" + }, + "exportedAt": { + "type": "string", + "format": "date-time", + "example": "2026-06-20T14:15:22Z" + } + } + }, + "variables": { + "type": "object", + "description": "Workflow-level variables, keyed by variable id. Emitted as stored \u2014 treat as plaintext configuration, not a secret store.", + "additionalProperties": true, + "example": {} + } + } + }, + "WorkflowExport": { + "type": "object", + "description": "A portable workflow export envelope. Pass this object straight back to POST /api/v1/workflows/import to recreate the workflow. Credential and password field values stored on blocks are redacted; `{{ENV_VAR}}` references and workflow variables are preserved as stored.", + "properties": { + "version": { + "type": "string", + "description": "Export format version.", + "enum": ["1.0"], + "example": "1.0" + }, + "exportedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of when the export was generated.", + "example": "2026-06-20T14:15:22Z" + }, + "workflow": { + "type": "object", + "description": "Identity of the exported workflow at the time of export.", + "properties": { + "id": { + "type": "string", + "example": "3b1f7c92-8d4e-4a6b-9c0d-5e2f8a714b36" + }, + "name": { + "type": "string", + "example": "Customer Support Agent" + }, + "description": { + "type": "string", + "nullable": true, + "example": "Routes incoming support tickets" + }, + "workspaceId": { + "type": "string", + "nullable": true, + "example": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64" + }, + "folderId": { + "type": "string", + "nullable": true, + "example": null + } + } + }, + "state": { + "$ref": "#/components/schemas/WorkflowExportState" + } + } + }, + "ImportedWorkflow": { + "type": "object", + "description": "The workflow created by an import.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier of the newly created workflow.", + "example": "7d2e9f14-3a6b-4c8d-b1e5-9f0a2c7d4e83" + }, + "name": { + "type": "string", + "description": "The workflow name after de-duplication against the target folder.", + "example": "Customer Support Agent (1)" + }, + "description": { + "type": "string", + "nullable": true, + "example": "Routes incoming support tickets" + }, + "workspaceId": { + "type": "string", + "example": "a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64" + }, + "folderId": { + "type": "string", + "nullable": true, + "example": null + }, + "createdAt": { + "type": "string", + "format": "date-time", + "example": "2026-06-20T14:15:22Z" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "example": "2026-06-20T14:15:22Z" + } + } } }, "responses": { diff --git a/apps/docs/package.json b/apps/docs/package.json index 8581c46ea31..da9850a4834 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -20,7 +20,6 @@ "@sim/db": "workspace:*", "@sim/emcn": "workspace:*", "@sim/workflow-renderer": "workspace:*", - "@vercel/og": "^0.6.5", "ai": "5.0.203", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -32,7 +31,6 @@ "lucide-react": "^0.511.0", "next": "16.2.11", "next-themes": "^0.4.6", - "postgres": "^3.4.5", "react": "19.2.4", "react-dom": "19.2.4", "remark-breaks": "^4.0.0", @@ -47,7 +45,7 @@ "@sim/tsconfig": "workspace:*", "@tailwindcss/postcss": "^4.0.12", "@types/mdx": "^2.0.13", - "@types/node": "^22.14.1", + "@types/node": "24.2.1", "@types/react": "^19.1.2", "@types/react-dom": "^19.0.4", "@typescript/native-preview": "7.0.0-dev.20260707.2", diff --git a/apps/sim/app/(landing)/CLAUDE.md b/apps/sim/app/(landing)/CLAUDE.md index 5d96ffdf9ed..f6b82e94dc3 100644 --- a/apps/sim/app/(landing)/CLAUDE.md +++ b/apps/sim/app/(landing)/CLAUDE.md @@ -44,7 +44,7 @@ Target: Lighthouse 95+ on mobile, LCP < 2.0s, CLS < 0.05, minimal hydration cost `page.tsx` owns the metadata (title, description, OG/Twitter, canonical, robots) - keep it the single source of truth and keep it aligned with the constitution's claim hierarchy. Beyond metadata: -- **One `

`, in the hero, containing "Sim" and "AI workspace".** Strict hierarchy below it: H2 per section, H3 for items within a section. Never skip levels, never add a second H1. +- **One `

`, in the hero, containing "AI workspace".** The brand carries in the title tag, the meta description, and the hero's `sr-only` summary, so the H1 itself is free to lead with the non-brand keywords people actually search ("AI workspace", "AI agents") rather than spending its first two words on "Sim is the". Whichever wording it uses, the hero's `sr-only` paragraph must still open by naming Sim. Strict hierarchy below it: H2 per section, H3 for items within a section. Never skip levels, never add a second H1. - **Semantic landmarks**: `
`, `
`, `