From 34f193c657d6e3498cb2816475403a6d87c5ed02 Mon Sep 17 00:00:00 2001 From: CJ Winslow Date: Mon, 26 Jan 2026 23:41:16 -0800 Subject: [PATCH] add config schema generation for IDE autocomplete - scripts/gen-config-schema.ts: generates JSON schema + TypeScript types from ClawdbotSchema - `pnpm schema:gen` - allow $schema key in config for IDE integration - Users can now add "$schema": "./schemas/clawdbot.schema.json" to their clawdbot.json for autocomplete without validation errors. - Document schema generation in configuration and scripts docs --- .gitignore | 2 ++ docs/gateway/configuration.md | 21 ++++++++++++ docs/scripts.md | 6 ++++ package.json | 2 ++ pnpm-lock.yaml | 63 +++++++++++++++++++++++++++++++++-- scripts/gen-config-schema.ts | 50 +++++++++++++++++++++++++++ src/config/zod-schema.ts | 1 + 7 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 scripts/gen-config-schema.ts diff --git a/.gitignore b/.gitignore index 9dc547c9c..4b78551fa 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ USER.md # local tooling .serena/ +schemas/*.d.ts +schemas/*.schema.json diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 5a00ea9cd..67d759e21 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -41,6 +41,27 @@ stay schema-driven across apps without hard-coded forms. Hints (labels, grouping, sensitive fields) ship alongside the schema so clients can render better forms without hard-coding config knowledge. +### IDE autocomplete + +Get full autocomplete and validation for `openclaw.json` in VS Code, Cursor, and other +JSON-Schema-aware editors by pointing to the generated schema: + +```json5 +{ + "$schema": "./schemas/moltbot.schema.json", + // ... rest of your config +} +``` + +Generate the schema (and a companion `.d.ts`) with: + +```bash +pnpm schema:gen +``` + +This writes `schemas/moltbot.schema.json` and `schemas/moltbot.d.ts` from the Zod source schema. +Both files are git-ignored; regenerate after pulling config schema changes. + ## Apply + restart (RPC) Use `config.apply` to validate + write the full config and restart the Gateway in one step. diff --git a/docs/scripts.md b/docs/scripts.md index 360a954c7..2ba7f59c0 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -25,6 +25,12 @@ Use these when a task is clearly tied to a script; otherwise prefer the CLI. Auth monitoring scripts are documented here: [/automation/auth-monitoring](/automation/auth-monitoring) +## Config schema generation + +- `scripts/gen-config-schema.ts`: generates `schemas/moltbot.schema.json` (JSON Schema) and `schemas/moltbot.d.ts` (TypeScript types) from the Zod config schema. +- Run via `pnpm schema:gen`. Both output files are git-ignored. +- See [IDE autocomplete](/gateway/configuration#ide-autocomplete) for usage. + ## When adding scripts - Keep scripts focused and documented. diff --git a/package.json b/package.json index 77211d865..b9d433178 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "protocol:gen": "node --import tsx scripts/protocol-gen.ts", "protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts", "protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/OpenClawProtocol/GatewayModels.swift", + "schema:gen": "node --import tsx scripts/gen-config-schema.ts", "canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh", "check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500" }, @@ -227,6 +228,7 @@ "@typescript/native-preview": "7.0.0-dev.20260124.1", "@vitest/coverage-v8": "^4.0.18", "docx-preview": "^0.3.7", + "json-schema-to-typescript": "^15.0.4", "lit": "^3.3.2", "lucide": "^0.563.0", "ollama": "^0.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95b940c97..0641a4f7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,6 +215,9 @@ importers: docx-preview: specifier: ^0.3.7 version: 0.3.7 + json-schema-to-typescript: + specifier: ^15.0.4 + version: 15.0.4 lit: specifier: ^3.3.2 version: 3.3.2 @@ -534,6 +537,10 @@ packages: zod: optional: true + '@apidevtools/json-schema-ref-parser@11.9.3': + resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} + engines: {node: '>= 16'} + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -1268,6 +1275,9 @@ packages: '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@keyv/bigmap@1.3.1': resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} engines: {node: '>= 18'} @@ -2716,12 +2726,18 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + '@types/lodash@4.17.23': + resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} + '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} @@ -3982,6 +3998,10 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} @@ -3996,6 +4016,11 @@ packages: resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} engines: {node: '>=16'} + json-schema-to-typescript@15.0.4: + resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} + engines: {node: '>=16.0.0'} + hasBin: true + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -4723,6 +4748,11 @@ packages: resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==} engines: {node: '>=12'} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + pretty-bytes@6.1.1: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} @@ -5589,6 +5619,12 @@ snapshots: optionalDependencies: zod: 4.3.6 + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.1 + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -6831,6 +6867,8 @@ snapshots: '@js-sdsl/ordered-map@4.4.2': {} + '@jsdevtools/ono@7.1.3': {} + '@keyv/bigmap@1.3.1(keyv@5.6.0)': dependencies: hashery: 1.4.0 @@ -8493,6 +8531,8 @@ snapshots: '@types/http-errors@2.0.5': {} + '@types/json-schema@7.0.15': {} + '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 @@ -8500,6 +8540,8 @@ snapshots: '@types/linkify-it@5.0.0': {} + '@types/lodash@4.17.23': {} + '@types/long@4.0.2': {} '@types/markdown-it@14.1.2': @@ -9985,6 +10027,10 @@ snapshots: js-tokens@9.0.1: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsbn@0.1.1: {} json-bigint@1.0.0: @@ -9998,6 +10044,18 @@ snapshots: '@babel/runtime': 7.28.6 ts-algebra: 2.0.0 + json-schema-to-typescript@15.0.4: + dependencies: + '@apidevtools/json-schema-ref-parser': 11.9.3 + '@types/json-schema': 7.0.15 + '@types/lodash': 4.17.23 + is-glob: 4.0.3 + js-yaml: 4.1.1 + lodash: 4.17.23 + minimist: 1.2.8 + prettier: 3.8.1 + tinyglobby: 0.2.15 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -10318,8 +10376,7 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist@1.2.8: - optional: true + minimist@1.2.8: {} minipass@7.1.2: {} @@ -10749,6 +10806,8 @@ snapshots: postgres@3.4.8: {} + prettier@3.8.1: {} + pretty-bytes@6.1.1: optional: true diff --git a/scripts/gen-config-schema.ts b/scripts/gen-config-schema.ts new file mode 100644 index 000000000..dd8cfca88 --- /dev/null +++ b/scripts/gen-config-schema.ts @@ -0,0 +1,50 @@ +#!/usr/bin/env bun +/** + * Generates schemas/clawdbot.schema.json and schemas/clawdbot.d.ts + * from the zod schema source. + * + * Usage: bun scripts/gen-config-schema.ts + */ +import { writeFile, mkdir } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { compile } from "json-schema-to-typescript"; +import { OpenClawSchema } from "../src/config/zod-schema.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootDir = join(__dirname, ".."); +const schemasDir = join(rootDir, "schemas"); + +async function main() { + await mkdir(schemasDir, { recursive: true }); + + // Generate JSON schema from zod + const jsonSchema = OpenClawSchema.toJSONSchema({ + target: "draft-07", + unrepresentable: "any", + }); + + const schemaPath = join(schemasDir, "openclaw.schema.json"); + await writeFile(schemaPath, JSON.stringify(jsonSchema, null, 2)); + console.log(`Wrote ${schemaPath}`); + + // Generate TypeScript types from JSON schema + const dts = await compile(jsonSchema as Record, "OpenClawConfig", { + bannerComment: `/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */`, + additionalProperties: false, + }); + + const dtsPath = join(schemasDir, "openclaw.d.ts"); + await writeFile(dtsPath, dts); + console.log(`Wrote ${dtsPath}`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 961ba8ecb..a4c206b0b 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -29,6 +29,7 @@ const NodeHostSchema = z export const OpenClawSchema = z .object({ + $schema: z.string().optional(), meta: z .object({ lastTouchedVersion: z.string().optional(),