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
This commit is contained in:
CJ Winslow 2026-01-26 23:41:16 -08:00
parent 28f8d00e9f
commit 34f193c657
7 changed files with 143 additions and 2 deletions

2
.gitignore vendored
View File

@ -71,3 +71,5 @@ USER.md
# local tooling # local tooling
.serena/ .serena/
schemas/*.d.ts
schemas/*.schema.json

View File

@ -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 Hints (labels, grouping, sensitive fields) ship alongside the schema so clients can render
better forms without hard-coding config knowledge. 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) ## Apply + restart (RPC)
Use `config.apply` to validate + write the full config and restart the Gateway in one step. Use `config.apply` to validate + write the full config and restart the Gateway in one step.

View File

@ -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: Auth monitoring scripts are documented here:
[/automation/auth-monitoring](/automation/auth-monitoring) [/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 ## When adding scripts
- Keep scripts focused and documented. - Keep scripts focused and documented.

View File

@ -143,6 +143,7 @@
"protocol:gen": "node --import tsx scripts/protocol-gen.ts", "protocol:gen": "node --import tsx scripts/protocol-gen.ts",
"protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.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", "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", "canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh",
"check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500" "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", "@typescript/native-preview": "7.0.0-dev.20260124.1",
"@vitest/coverage-v8": "^4.0.18", "@vitest/coverage-v8": "^4.0.18",
"docx-preview": "^0.3.7", "docx-preview": "^0.3.7",
"json-schema-to-typescript": "^15.0.4",
"lit": "^3.3.2", "lit": "^3.3.2",
"lucide": "^0.563.0", "lucide": "^0.563.0",
"ollama": "^0.6.3", "ollama": "^0.6.3",

63
pnpm-lock.yaml generated
View File

@ -215,6 +215,9 @@ importers:
docx-preview: docx-preview:
specifier: ^0.3.7 specifier: ^0.3.7
version: 0.3.7 version: 0.3.7
json-schema-to-typescript:
specifier: ^15.0.4
version: 15.0.4
lit: lit:
specifier: ^3.3.2 specifier: ^3.3.2
version: 3.3.2 version: 3.3.2
@ -534,6 +537,10 @@ packages:
zod: zod:
optional: true 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': '@aws-crypto/crc32@5.2.0':
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@ -1268,6 +1275,9 @@ packages:
'@js-sdsl/ordered-map@4.4.2': '@js-sdsl/ordered-map@4.4.2':
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} 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': '@keyv/bigmap@1.3.1':
resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
@ -2716,12 +2726,18 @@ packages:
'@types/http-errors@2.0.5': '@types/http-errors@2.0.5':
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/jsonwebtoken@9.0.10': '@types/jsonwebtoken@9.0.10':
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
'@types/linkify-it@5.0.0': '@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/lodash@4.17.23':
resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==}
'@types/long@4.0.2': '@types/long@4.0.2':
resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==}
@ -3982,6 +3998,10 @@ packages:
js-tokens@9.0.1: js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
jsbn@0.1.1: jsbn@0.1.1:
resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
@ -3996,6 +4016,11 @@ packages:
resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
engines: {node: '>=16'} 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: json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@ -4723,6 +4748,11 @@ packages:
resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==} resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==}
engines: {node: '>=12'} engines: {node: '>=12'}
prettier@3.8.1:
resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
engines: {node: '>=14'}
hasBin: true
pretty-bytes@6.1.1: pretty-bytes@6.1.1:
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
engines: {node: ^14.13.1 || >=16.0.0} engines: {node: ^14.13.1 || >=16.0.0}
@ -5589,6 +5619,12 @@ snapshots:
optionalDependencies: optionalDependencies:
zod: 4.3.6 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': '@aws-crypto/crc32@5.2.0':
dependencies: dependencies:
'@aws-crypto/util': 5.2.0 '@aws-crypto/util': 5.2.0
@ -6831,6 +6867,8 @@ snapshots:
'@js-sdsl/ordered-map@4.4.2': {} '@js-sdsl/ordered-map@4.4.2': {}
'@jsdevtools/ono@7.1.3': {}
'@keyv/bigmap@1.3.1(keyv@5.6.0)': '@keyv/bigmap@1.3.1(keyv@5.6.0)':
dependencies: dependencies:
hashery: 1.4.0 hashery: 1.4.0
@ -8493,6 +8531,8 @@ snapshots:
'@types/http-errors@2.0.5': {} '@types/http-errors@2.0.5': {}
'@types/json-schema@7.0.15': {}
'@types/jsonwebtoken@9.0.10': '@types/jsonwebtoken@9.0.10':
dependencies: dependencies:
'@types/ms': 2.1.0 '@types/ms': 2.1.0
@ -8500,6 +8540,8 @@ snapshots:
'@types/linkify-it@5.0.0': {} '@types/linkify-it@5.0.0': {}
'@types/lodash@4.17.23': {}
'@types/long@4.0.2': {} '@types/long@4.0.2': {}
'@types/markdown-it@14.1.2': '@types/markdown-it@14.1.2':
@ -9985,6 +10027,10 @@ snapshots:
js-tokens@9.0.1: {} js-tokens@9.0.1: {}
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
jsbn@0.1.1: {} jsbn@0.1.1: {}
json-bigint@1.0.0: json-bigint@1.0.0:
@ -9998,6 +10044,18 @@ snapshots:
'@babel/runtime': 7.28.6 '@babel/runtime': 7.28.6
ts-algebra: 2.0.0 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@0.4.1: {}
json-schema-traverse@1.0.0: {} json-schema-traverse@1.0.0: {}
@ -10318,8 +10376,7 @@ snapshots:
dependencies: dependencies:
brace-expansion: 2.0.2 brace-expansion: 2.0.2
minimist@1.2.8: minimist@1.2.8: {}
optional: true
minipass@7.1.2: {} minipass@7.1.2: {}
@ -10749,6 +10806,8 @@ snapshots:
postgres@3.4.8: {} postgres@3.4.8: {}
prettier@3.8.1: {}
pretty-bytes@6.1.1: pretty-bytes@6.1.1:
optional: true optional: true

View File

@ -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<string, unknown>, "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);
});

View File

@ -29,6 +29,7 @@ const NodeHostSchema = z
export const OpenClawSchema = z export const OpenClawSchema = z
.object({ .object({
$schema: z.string().optional(),
meta: z meta: z
.object({ .object({
lastTouchedVersion: z.string().optional(), lastTouchedVersion: z.string().optional(),