@neural-tools/create-json-plugin 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +40 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +132 -0
- package/dist/index.mjs +132 -0
- package/package.json +52 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Luke Amy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @neural-tools/create-json-plugin
|
|
2
|
+
|
|
3
|
+
Scaffold Claude plugins that include:
|
|
4
|
+
|
|
5
|
+
- A JSON-output skill (`skills/<skill-name>/SKILL.md`)
|
|
6
|
+
- A custom JSON LSP config (`.lsp.json`)
|
|
7
|
+
- A local schema (`schemas/*.schema.json`)
|
|
8
|
+
- Hook scripts (`hooks/`) that enforce schema validation on writes/edits
|
|
9
|
+
- A starter config file validated against that schema
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm --filter @neural-tools/create-json-plugin build
|
|
15
|
+
node packages/create-json-plugin/dist/index.js my-plugin
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or after publishing:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @neural-tools/create-json-plugin my-plugin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Defaults
|
|
25
|
+
|
|
26
|
+
- Skill name: `generate-json-config`
|
|
27
|
+
- Output config file: `config.json`
|
|
28
|
+
- Schema file: `config.schema.json`
|
|
29
|
+
- Output directory: `./claude/plugins`
|
|
30
|
+
|
|
31
|
+
## CLI options
|
|
32
|
+
|
|
33
|
+
- `-d, --description <desc>` Plugin description
|
|
34
|
+
- `-o, --output <dir>` Output directory for plugins
|
|
35
|
+
- `--skill <name>` Skill name to create
|
|
36
|
+
- `--file-name <name>` Target JSON config file name
|
|
37
|
+
- `--schema-name <name>` Schema file name
|
|
38
|
+
- `--version <version>` Plugin version
|
|
39
|
+
- `--author <name>` Plugin author
|
|
40
|
+
- `--dry-run` Preview without writing files
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface GenerateJsonPluginOptions {
|
|
3
|
+
description?: string;
|
|
4
|
+
output?: string;
|
|
5
|
+
skill?: string;
|
|
6
|
+
fileName?: string;
|
|
7
|
+
schemaName?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
author?: string;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare function createJsonPlugin(pluginName: string | undefined, options: GenerateJsonPluginOptions): Promise<void>;
|
|
13
|
+
|
|
14
|
+
export { createJsonPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface GenerateJsonPluginOptions {
|
|
3
|
+
description?: string;
|
|
4
|
+
output?: string;
|
|
5
|
+
skill?: string;
|
|
6
|
+
fileName?: string;
|
|
7
|
+
schemaName?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
author?: string;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare function createJsonPlugin(pluginName: string | undefined, options: GenerateJsonPluginOptions): Promise<void>;
|
|
13
|
+
|
|
14
|
+
export { createJsonPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var T=Object.create;var f=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var K=(i,e)=>{for(var t in e)f(i,t,{get:e[t],enumerable:!0})},D=(i,e,t,p)=>{if(e&&typeof e=="object"||typeof e=="function")for(let c of q(e))!H.call(i,c)&&c!==t&&f(i,c,{get:()=>e[c],enumerable:!(p=U(e,c))||p.enumerable});return i};var P=(i,e,t)=>(t=i!=null?T(W(i)):{},D(e||!i||!i.__esModule?f(t,"default",{value:i,enumerable:!0}):t,i)),V=i=>D(f({},"__esModule",{value:!0}),i);var z={};K(z,{createJsonPlugin:()=>I});module.exports=V(z);var o=P(require("path")),n=P(require("fs-extra")),v=P(require("inquirer")),L=require("commander"),l=require("@neural-tools/core"),b=new L.Command;b.name("create-neural-json-plugin").description("Create a Claude plugin with a JSON-output skill and custom JSON LSP validation").argument("[plugin-name]","Name of the plugin").option("-d, --description <desc>","Description of the plugin").option("-o, --output <dir>","Output directory","./claude/plugins").option("--skill <name>","Skill name","generate-json-config").option("--file-name <name>","Name of the JSON config file","config.json").option("--schema-name <name>","Name of the JSON schema file","config.schema.json").option("--version <version>","Plugin version","0.1.0").option("--author <author>","Plugin author").option("--dry-run","Preview without creating files",!1).action(async(i,e)=>{await I(i,e)});async function I(i,e){l.logger.header("Create JSON Plugin");let t=i;if(t||(t=(await v.default.prompt([{type:"input",name:"pluginName",message:"Plugin name:",default:"json-config-plugin",validate:h=>!h||h.trim().length===0?"Plugin name is required":!0}])).pluginName),!t)throw new Error("Plugin name is required");let p=e.description;p||(p=(await v.default.prompt([{type:"input",name:"description",message:"Plugin description:",default:`${t} plugin with JSON skill + schema validation`}])).description);let c=p||`${t} plugin with JSON skill + schema validation`,u=e.skill||"generate-json-config",a=e.fileName||"config.json",d=e.schemaName||"config.schema.json",s=o.default.resolve(e.output||"./claude/plugins",t),k=o.default.join(s,".claude-plugin"),g=o.default.join(k,"plugin.json"),O=o.default.join(s,"schemas"),j=o.default.join(O,d),E=o.default.join(s,"skills",u),w=o.default.join(E,"SKILL.md"),S=o.default.join(s,"hooks"),_=o.default.join(S,"scripts"),N=o.default.join(S,"hooks.json"),m=o.default.join(_,"validate-json-config.py"),J=o.default.join(_,"ensure-json-lsp.sh"),C=o.default.join(s,"examples"),$=o.default.join(C,a),y=o.default.join(s,".lsp.json");if(e.dryRun){l.logger.section("Dry Run",[`Plugin directory: ${s}`,`Manifest: ${g}`,`LSP config: ${y}`,`Schema: ${j}`,`Skill: ${w}`,`Hooks config: ${N}`,`Validation hook: ${m}`,`Starter config: ${$}`]);return}if(await n.default.pathExists(g)){let{overwrite:r}=await v.default.prompt([{type:"confirm",name:"overwrite",message:`Plugin ${t} already exists at ${s}. Overwrite?`,default:!1}]);if(!r){l.logger.warn("Cancelled");return}await n.default.remove(s)}l.logger.startSpinner("Scaffolding plugin...");try{await n.default.ensureDir(k),await n.default.ensureDir(O),await n.default.ensureDir(E),await n.default.ensureDir(S),await n.default.ensureDir(_),await n.default.ensureDir(C);let r={name:t,version:e.version||"0.1.0",description:c};e.author&&(r.author=e.author),await n.default.writeJSON(g,r,{spaces:2});let h={json:{command:"vscode-json-languageserver",args:["--stdio"],extensionToLanguage:{".json":"json"},settings:{json:{schemas:[{fileMatch:[`**/${a}`],url:`file://\${CLAUDE_PLUGIN_ROOT}/schemas/${d}`}],validate:{enable:!0}}}}};await n.default.writeJSON(y,h,{spaces:2});let x={hooks:{SessionStart:[{hooks:[{type:"command",command:"bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/ensure-json-lsp.sh",timeout:60}]}],PreToolUse:[{matcher:"Edit|Write|MultiEdit",hooks:[{type:"command",command:"python3 ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/validate-json-config.py",timeout:15}]}]}};await n.default.writeJSON(N,x,{spaces:2});let R={$schema:"http://json-schema.org/draft-07/schema#",title:a,description:`Schema for ${a}`,type:"object",required:["version","settings"],properties:{version:{type:"string"},settings:{type:"object",minProperties:1,properties:{enabled:{type:"boolean"},mode:{type:"string"}},additionalProperties:!0},items:{type:"array",items:{type:"object",required:["name","value"],properties:{name:{type:"string"},value:{}},additionalProperties:!0}}},additionalProperties:!0};await n.default.writeJSON(j,R,{spaces:2});let G=`#!/usr/bin/env python3
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
TARGET_FILE = "${a}"
|
|
9
|
+
SCHEMA_FILE = "${d}"
|
|
10
|
+
|
|
11
|
+
def load_schema(plugin_root: str) -> dict:
|
|
12
|
+
schema_path = Path(plugin_root) / "schemas" / SCHEMA_FILE
|
|
13
|
+
with open(schema_path, "r", encoding="utf-8") as f:
|
|
14
|
+
return json.load(f)
|
|
15
|
+
|
|
16
|
+
def validate_with_jsonschema(config: dict, plugin_root: str) -> list[str]:
|
|
17
|
+
import jsonschema
|
|
18
|
+
|
|
19
|
+
schema = load_schema(plugin_root)
|
|
20
|
+
validator = jsonschema.Draft7Validator(schema)
|
|
21
|
+
errors = []
|
|
22
|
+
for error in sorted(validator.iter_errors(config), key=lambda e: str(e.path)):
|
|
23
|
+
path = ".".join(str(p) for p in error.path) or "(root)"
|
|
24
|
+
errors.append(f" - {path}: {error.message}")
|
|
25
|
+
return errors
|
|
26
|
+
|
|
27
|
+
def validate_basic(config: dict) -> list[str]:
|
|
28
|
+
errors = []
|
|
29
|
+
if "version" not in config:
|
|
30
|
+
errors.append(" - Missing required field: version")
|
|
31
|
+
if "settings" not in config:
|
|
32
|
+
errors.append(" - Missing required field: settings")
|
|
33
|
+
if "settings" in config and not isinstance(config["settings"], dict):
|
|
34
|
+
errors.append(" - settings must be an object")
|
|
35
|
+
return errors
|
|
36
|
+
|
|
37
|
+
def main() -> None:
|
|
38
|
+
try:
|
|
39
|
+
hook_data = json.load(sys.stdin)
|
|
40
|
+
except (json.JSONDecodeError, EOFError):
|
|
41
|
+
sys.exit(0)
|
|
42
|
+
|
|
43
|
+
if hook_data.get("tool_name") not in ("Write", "Edit", "MultiEdit"):
|
|
44
|
+
sys.exit(0)
|
|
45
|
+
|
|
46
|
+
tool_input = hook_data.get("tool_input", {})
|
|
47
|
+
file_path = tool_input.get("file_path", "")
|
|
48
|
+
content = tool_input.get("content", "")
|
|
49
|
+
|
|
50
|
+
if not file_path.endswith(TARGET_FILE):
|
|
51
|
+
sys.exit(0)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
config = json.loads(content)
|
|
55
|
+
except json.JSONDecodeError as e:
|
|
56
|
+
print(json.dumps({"decision": "block", "reason": f"JSON parse error in {Path(file_path).name}: {e}"}))
|
|
57
|
+
sys.exit(0)
|
|
58
|
+
|
|
59
|
+
plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", str(Path(__file__).parent.parent.parent))
|
|
60
|
+
try:
|
|
61
|
+
errors = validate_with_jsonschema(config, plugin_root)
|
|
62
|
+
except Exception:
|
|
63
|
+
errors = validate_basic(config)
|
|
64
|
+
|
|
65
|
+
if errors:
|
|
66
|
+
lines = [f"JSON config validation failed for {Path(file_path).name}:"]
|
|
67
|
+
lines.extend(errors)
|
|
68
|
+
lines.append(f"Fix validation errors to match schema: {SCHEMA_FILE}")
|
|
69
|
+
print(json.dumps({"decision": "block", "reason": "\\n".join(lines)}))
|
|
70
|
+
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
75
|
+
`;await n.default.writeFile(m,G,"utf-8"),await n.default.chmod(m,493),await n.default.writeFile(J,`#!/bin/bash
|
|
76
|
+
WARNINGS=()
|
|
77
|
+
|
|
78
|
+
if ! command -v vscode-json-languageserver &>/dev/null; then
|
|
79
|
+
if command -v npm &>/dev/null; then
|
|
80
|
+
echo "Installing vscode-json-languageserver..."
|
|
81
|
+
npm install -g vscode-json-languageserver --silent
|
|
82
|
+
echo "JSON language server installed. Restart Claude Code to activate schema validation."
|
|
83
|
+
else
|
|
84
|
+
WARNINGS+=("vscode-json-languageserver not found. Install Node.js then run: npm install -g vscode-json-languageserver")
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ \${#WARNINGS[@]} -gt 0 ]]; then
|
|
89
|
+
echo ""
|
|
90
|
+
echo "Plugin setup needed:"
|
|
91
|
+
for w in "\${WARNINGS[@]}"; do
|
|
92
|
+
echo " - $w"
|
|
93
|
+
done
|
|
94
|
+
echo ""
|
|
95
|
+
fi
|
|
96
|
+
`,"utf-8"),await n.default.chmod(J,493);let A=`---
|
|
97
|
+
name: ${u}
|
|
98
|
+
description: Generate ${a} JSON files that validate against ${d}
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
# ${u}
|
|
102
|
+
|
|
103
|
+
Generate a valid \`${a}\` file.
|
|
104
|
+
|
|
105
|
+
## Rules
|
|
106
|
+
|
|
107
|
+
1. Output strict JSON only when producing the final config (no comments, no trailing commas).
|
|
108
|
+
2. Ensure the output matches schema at \`${d}\`.
|
|
109
|
+
3. Include required top-level keys: \`version\` and \`settings\`.
|
|
110
|
+
4. Keep all values syntactically valid JSON and consistent with the schema.
|
|
111
|
+
|
|
112
|
+
## Output
|
|
113
|
+
|
|
114
|
+
Return the JSON in a fenced block with \`json\` language and save it as \`${a}\`.
|
|
115
|
+
`;await n.default.writeFile(w,A,"utf-8");let F={version:"1.0.0",settings:{enabled:!0,mode:"standard"},items:[{name:"example",value:"demo"}]};await n.default.writeJSON($,F,{spaces:2});let M=`# ${t}
|
|
116
|
+
|
|
117
|
+
Generated by @neural-tools/create-json-plugin.
|
|
118
|
+
|
|
119
|
+
## What this plugin includes
|
|
120
|
+
|
|
121
|
+
- \`.lsp.json\` using \`vscode-json-languageserver\`
|
|
122
|
+
- JSON schema at \`schemas/${d}\`
|
|
123
|
+
- Skill at \`skills/${u}/SKILL.md\` for generating \`${a}\`
|
|
124
|
+
- Hooks at \`hooks/hooks.json\` that enforce schema validation on edits/writes
|
|
125
|
+
- Starter config at \`examples/${a}\`
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
|
|
129
|
+
Install the JSON language server if needed:
|
|
130
|
+
|
|
131
|
+
\`npm install -g vscode-json-languageserver\`
|
|
132
|
+
`;await n.default.writeFile(o.default.join(s,"README.md"),M,"utf-8"),l.logger.succeedSpinner("Plugin created"),l.logger.section("Created",[`Plugin: ${s}`,`Manifest: ${g}`,`LSP config: ${y}`,`Schema: ${j}`,`Skill: ${w}`,`Hooks config: ${N}`,`Validation hook: ${m}`,`Starter config: ${$}`])}catch(r){throw l.logger.failSpinner("Failed to create plugin"),r}}b.parse();0&&(module.exports={createJsonPlugin});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import n from"path";import e from"fs-extra";import N from"inquirer";import{Command as R}from"commander";import{logger as r}from"@neural-tools/core";var E=new R;E.name("create-neural-json-plugin").description("Create a Claude plugin with a JSON-output skill and custom JSON LSP validation").argument("[plugin-name]","Name of the plugin").option("-d, --description <desc>","Description of the plugin").option("-o, --output <dir>","Output directory","./claude/plugins").option("--skill <name>","Skill name","generate-json-config").option("--file-name <name>","Name of the JSON config file","config.json").option("--schema-name <name>","Name of the JSON schema file","config.schema.json").option("--version <version>","Plugin version","0.1.0").option("--author <author>","Plugin author").option("--dry-run","Preview without creating files",!1).action(async(g,i)=>{await G(g,i)});async function G(g,i){r.header("Create JSON Plugin");let s=g;if(s||(s=(await N.prompt([{type:"input",name:"pluginName",message:"Plugin name:",default:"json-config-plugin",validate:u=>!u||u.trim().length===0?"Plugin name is required":!0}])).pluginName),!s)throw new Error("Plugin name is required");let m=i.description;m||(m=(await N.prompt([{type:"input",name:"description",message:"Plugin description:",default:`${s} plugin with JSON skill + schema validation`}])).description);let J=m||`${s} plugin with JSON skill + schema validation`,c=i.skill||"generate-json-config",o=i.fileName||"config.json",l=i.schemaName||"config.schema.json",t=n.resolve(i.output||"./claude/plugins",s),$=n.join(t,".claude-plugin"),p=n.join($,"plugin.json"),y=n.join(t,"schemas"),h=n.join(y,l),P=n.join(t,"skills",c),f=n.join(P,"SKILL.md"),v=n.join(t,"hooks"),j=n.join(v,"scripts"),w=n.join(v,"hooks.json"),d=n.join(j,"validate-json-config.py"),k=n.join(j,"ensure-json-lsp.sh"),O=n.join(t,"examples"),S=n.join(O,o),_=n.join(t,".lsp.json");if(i.dryRun){r.section("Dry Run",[`Plugin directory: ${t}`,`Manifest: ${p}`,`LSP config: ${_}`,`Schema: ${h}`,`Skill: ${f}`,`Hooks config: ${w}`,`Validation hook: ${d}`,`Starter config: ${S}`]);return}if(await e.pathExists(p)){let{overwrite:a}=await N.prompt([{type:"confirm",name:"overwrite",message:`Plugin ${s} already exists at ${t}. Overwrite?`,default:!1}]);if(!a){r.warn("Cancelled");return}await e.remove(t)}r.startSpinner("Scaffolding plugin...");try{await e.ensureDir($),await e.ensureDir(y),await e.ensureDir(P),await e.ensureDir(v),await e.ensureDir(j),await e.ensureDir(O);let a={name:s,version:i.version||"0.1.0",description:J};i.author&&(a.author=i.author),await e.writeJSON(p,a,{spaces:2});let u={json:{command:"vscode-json-languageserver",args:["--stdio"],extensionToLanguage:{".json":"json"},settings:{json:{schemas:[{fileMatch:[`**/${o}`],url:`file://\${CLAUDE_PLUGIN_ROOT}/schemas/${l}`}],validate:{enable:!0}}}}};await e.writeJSON(_,u,{spaces:2});let C={hooks:{SessionStart:[{hooks:[{type:"command",command:"bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/ensure-json-lsp.sh",timeout:60}]}],PreToolUse:[{matcher:"Edit|Write|MultiEdit",hooks:[{type:"command",command:"python3 ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/validate-json-config.py",timeout:15}]}]}};await e.writeJSON(w,C,{spaces:2});let D={$schema:"http://json-schema.org/draft-07/schema#",title:o,description:`Schema for ${o}`,type:"object",required:["version","settings"],properties:{version:{type:"string"},settings:{type:"object",minProperties:1,properties:{enabled:{type:"boolean"},mode:{type:"string"}},additionalProperties:!0},items:{type:"array",items:{type:"object",required:["name","value"],properties:{name:{type:"string"},value:{}},additionalProperties:!0}}},additionalProperties:!0};await e.writeJSON(h,D,{spaces:2});let L=`#!/usr/bin/env python3
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
TARGET_FILE = "${o}"
|
|
9
|
+
SCHEMA_FILE = "${l}"
|
|
10
|
+
|
|
11
|
+
def load_schema(plugin_root: str) -> dict:
|
|
12
|
+
schema_path = Path(plugin_root) / "schemas" / SCHEMA_FILE
|
|
13
|
+
with open(schema_path, "r", encoding="utf-8") as f:
|
|
14
|
+
return json.load(f)
|
|
15
|
+
|
|
16
|
+
def validate_with_jsonschema(config: dict, plugin_root: str) -> list[str]:
|
|
17
|
+
import jsonschema
|
|
18
|
+
|
|
19
|
+
schema = load_schema(plugin_root)
|
|
20
|
+
validator = jsonschema.Draft7Validator(schema)
|
|
21
|
+
errors = []
|
|
22
|
+
for error in sorted(validator.iter_errors(config), key=lambda e: str(e.path)):
|
|
23
|
+
path = ".".join(str(p) for p in error.path) or "(root)"
|
|
24
|
+
errors.append(f" - {path}: {error.message}")
|
|
25
|
+
return errors
|
|
26
|
+
|
|
27
|
+
def validate_basic(config: dict) -> list[str]:
|
|
28
|
+
errors = []
|
|
29
|
+
if "version" not in config:
|
|
30
|
+
errors.append(" - Missing required field: version")
|
|
31
|
+
if "settings" not in config:
|
|
32
|
+
errors.append(" - Missing required field: settings")
|
|
33
|
+
if "settings" in config and not isinstance(config["settings"], dict):
|
|
34
|
+
errors.append(" - settings must be an object")
|
|
35
|
+
return errors
|
|
36
|
+
|
|
37
|
+
def main() -> None:
|
|
38
|
+
try:
|
|
39
|
+
hook_data = json.load(sys.stdin)
|
|
40
|
+
except (json.JSONDecodeError, EOFError):
|
|
41
|
+
sys.exit(0)
|
|
42
|
+
|
|
43
|
+
if hook_data.get("tool_name") not in ("Write", "Edit", "MultiEdit"):
|
|
44
|
+
sys.exit(0)
|
|
45
|
+
|
|
46
|
+
tool_input = hook_data.get("tool_input", {})
|
|
47
|
+
file_path = tool_input.get("file_path", "")
|
|
48
|
+
content = tool_input.get("content", "")
|
|
49
|
+
|
|
50
|
+
if not file_path.endswith(TARGET_FILE):
|
|
51
|
+
sys.exit(0)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
config = json.loads(content)
|
|
55
|
+
except json.JSONDecodeError as e:
|
|
56
|
+
print(json.dumps({"decision": "block", "reason": f"JSON parse error in {Path(file_path).name}: {e}"}))
|
|
57
|
+
sys.exit(0)
|
|
58
|
+
|
|
59
|
+
plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", str(Path(__file__).parent.parent.parent))
|
|
60
|
+
try:
|
|
61
|
+
errors = validate_with_jsonschema(config, plugin_root)
|
|
62
|
+
except Exception:
|
|
63
|
+
errors = validate_basic(config)
|
|
64
|
+
|
|
65
|
+
if errors:
|
|
66
|
+
lines = [f"JSON config validation failed for {Path(file_path).name}:"]
|
|
67
|
+
lines.extend(errors)
|
|
68
|
+
lines.append(f"Fix validation errors to match schema: {SCHEMA_FILE}")
|
|
69
|
+
print(json.dumps({"decision": "block", "reason": "\\n".join(lines)}))
|
|
70
|
+
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
75
|
+
`;await e.writeFile(d,L,"utf-8"),await e.chmod(d,493),await e.writeFile(k,`#!/bin/bash
|
|
76
|
+
WARNINGS=()
|
|
77
|
+
|
|
78
|
+
if ! command -v vscode-json-languageserver &>/dev/null; then
|
|
79
|
+
if command -v npm &>/dev/null; then
|
|
80
|
+
echo "Installing vscode-json-languageserver..."
|
|
81
|
+
npm install -g vscode-json-languageserver --silent
|
|
82
|
+
echo "JSON language server installed. Restart Claude Code to activate schema validation."
|
|
83
|
+
else
|
|
84
|
+
WARNINGS+=("vscode-json-languageserver not found. Install Node.js then run: npm install -g vscode-json-languageserver")
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ \${#WARNINGS[@]} -gt 0 ]]; then
|
|
89
|
+
echo ""
|
|
90
|
+
echo "Plugin setup needed:"
|
|
91
|
+
for w in "\${WARNINGS[@]}"; do
|
|
92
|
+
echo " - $w"
|
|
93
|
+
done
|
|
94
|
+
echo ""
|
|
95
|
+
fi
|
|
96
|
+
`,"utf-8"),await e.chmod(k,493);let b=`---
|
|
97
|
+
name: ${c}
|
|
98
|
+
description: Generate ${o} JSON files that validate against ${l}
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
# ${c}
|
|
102
|
+
|
|
103
|
+
Generate a valid \`${o}\` file.
|
|
104
|
+
|
|
105
|
+
## Rules
|
|
106
|
+
|
|
107
|
+
1. Output strict JSON only when producing the final config (no comments, no trailing commas).
|
|
108
|
+
2. Ensure the output matches schema at \`${l}\`.
|
|
109
|
+
3. Include required top-level keys: \`version\` and \`settings\`.
|
|
110
|
+
4. Keep all values syntactically valid JSON and consistent with the schema.
|
|
111
|
+
|
|
112
|
+
## Output
|
|
113
|
+
|
|
114
|
+
Return the JSON in a fenced block with \`json\` language and save it as \`${o}\`.
|
|
115
|
+
`;await e.writeFile(f,b,"utf-8");let I={version:"1.0.0",settings:{enabled:!0,mode:"standard"},items:[{name:"example",value:"demo"}]};await e.writeJSON(S,I,{spaces:2});let x=`# ${s}
|
|
116
|
+
|
|
117
|
+
Generated by @neural-tools/create-json-plugin.
|
|
118
|
+
|
|
119
|
+
## What this plugin includes
|
|
120
|
+
|
|
121
|
+
- \`.lsp.json\` using \`vscode-json-languageserver\`
|
|
122
|
+
- JSON schema at \`schemas/${l}\`
|
|
123
|
+
- Skill at \`skills/${c}/SKILL.md\` for generating \`${o}\`
|
|
124
|
+
- Hooks at \`hooks/hooks.json\` that enforce schema validation on edits/writes
|
|
125
|
+
- Starter config at \`examples/${o}\`
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
|
|
129
|
+
Install the JSON language server if needed:
|
|
130
|
+
|
|
131
|
+
\`npm install -g vscode-json-languageserver\`
|
|
132
|
+
`;await e.writeFile(n.join(t,"README.md"),x,"utf-8"),r.succeedSpinner("Plugin created"),r.section("Created",[`Plugin: ${t}`,`Manifest: ${p}`,`LSP config: ${_}`,`Schema: ${h}`,`Skill: ${f}`,`Hooks config: ${w}`,`Validation hook: ${d}`,`Starter config: ${S}`])}catch(a){throw r.failSpinner("Failed to create plugin"),a}}E.parse();export{G as createJsonPlugin};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neural-tools/create-json-plugin",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Scaffold Claude plugins with JSON-output skills and custom JSON LSP validation",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-neural-json-plugin": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"license": "SEE LICENSE IN ../../LICENSE.md",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/MacLeanLuke/neural-tools.git",
|
|
17
|
+
"directory": "packages/create-json-plugin"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/MacLeanLuke/neural-tools#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/MacLeanLuke/neural-tools/issues"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"neural-tools",
|
|
25
|
+
"claude",
|
|
26
|
+
"plugin",
|
|
27
|
+
"json",
|
|
28
|
+
"lsp",
|
|
29
|
+
"generator"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^12.0.0",
|
|
33
|
+
"fs-extra": "^11.2.0",
|
|
34
|
+
"inquirer": "^9.2.12",
|
|
35
|
+
"@neural-tools/core": "0.1.7"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/fs-extra": "^11.0.4",
|
|
39
|
+
"@types/inquirer": "^9.0.7",
|
|
40
|
+
"@types/node": "^20.11.5",
|
|
41
|
+
"typescript": "^5.3.3"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup && chmod +x dist/index.js",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"clean": "rm -rf dist",
|
|
50
|
+
"test": "echo 'Tests coming soon'"
|
|
51
|
+
}
|
|
52
|
+
}
|