| name | json-flat-tool |
| description | JSON flat view, schema inference, and edit tool.
⚠️ CRITICAL: ALWAYS use jstool for JSON files - NEVER use read/edit/cat on *.json!
**Strongest trigger — editing any JSON config file**: when the user asks to change, add, set, update, or remove a field in a `.json` configuration (config.json, settings.json, package.json, tsconfig.json, appsettings.json, .eslintrc, openclaw.json, mcp.json, any tool's `*.json` config), invoke this skill instead of opening the file in a text editor. Reading the whole file, hand-editing braces, or rewriting it from memory all break formatting and risk silent corruption — jstool gives you a structural diff preview before applying.
Also use when the user provides JSON data in ANY form and wants to explore its structure, infer its schema, modify fields, or search keys/values. This includes: - JSON files on disk (view, inspect, search) - JSON configuration files (any *.json config — see above) - Editing / updating fields (set key, delete key, insert array element,
bulk update, modify nested value)
- Inline JSON pasted into chat - HTTP / REST API responses (curl output, Postman captures, fetch() results) - WebSocket (WSS) message payloads captured from a browser or proxy - Polymarket or any exchange orderbook / market-data snapshots - Any API protocol payload (CLOB, AMM, streaming events, RPC responses)
Default behavior masks values whose key name looks like a secret (apiKey, token, password, etc.); pass `-R` / `--reveal` to disable masking for one invocation. Sensitive-key patterns and mask format live in `<skill-dir>/config.json` and can be edited without touching code.
Triggers on: view json, edit json, json schema, json structure, set json field, jstool, analyze json, flat view, orderbook, json flat, modify json, update json, change json, json config, edit config, modify config, update config, set config field, change config, config.json, settings.json, package.json, tsconfig.json, delete json field, insert json, add json key, json edit, api response, http response, rest api json, websocket message, wss payload, ws frame, api payload, protocol message, clob orderbook, market data json, analyze api, inspect response, parse response.
|
| allowed-tools | Bash |
| argument-hint | [json-file | - (stdin) | inline JSON | api-url] |
JSON Flat Tool
⚠️ MANDATORY: Use jstool instead of read/edit/cat for JSON files!
Before using read/edit on *.json files, ask yourself:
"Should I use jstool instead?"
- View:
python3 ~/.agents/skills/json-flat-tool/jstool.py view <file> -s
- Edit:
python3 ~/.agents/skills/json-flat-tool/jstool.py set <path> <value> <file> -f
- Search:
python3 ~/.agents/skills/json-flat-tool/jstool.py find <pattern> <file>
Note on hooks: This skill previously attempted to use PreToolUse hooks
to intercept Read/Edit calls on JSON files. However, harness hooks generally
cannot match on tool arguments (like file extensions) — matchers only
support tool name patterns (e.g. "Read", "Edit"), not argument-based
filtering (e.g. "read(*.json)"). For argument-level validation, hooks
must use a command script that parses tool_input JSON itself.
A single-script tool (jstool.py) for viewing, inspecting, and editing JSON data.
Script path: ~/.claude/skills/json-flat-tool/jstool.py
Invocation
python3 ~/.claude/skills/json-flat-tool/jstool.py <command> [args]
Commands
| Command | Description |
|---|
view [file] [opts] | Flat path/type/value display |
schema [file] [--title T] | Infer JSON Schema Draft 7 |
set <path> <value> [file] [-f] | Set a field value |
<path> = <value> [file] [-f] | Same, B-style syntax |
before <path> <value> [file] [-f] | Insert before array element |
after <path> <value> [file] [-f] | Insert after array element |
del <path> [file] [-f] | Delete a key or element |
set-null <path> [file] [-f] | Set a field to null |
copy <src> <dst> [file] [-f] | Deep-clone a subtree to a new path |
merge <path> <patch.json> [file] [-f] | Deep-merge a JSON file into a path |
find <pattern> [file] [opts] | Search paths and values by regex or glob |
Omit [file] to read from stdin.
view Options
| Flag | Unit | Description |
|---|
-s | — | Schema mode: collapse [N]→[*], deduplicate, hide values |
-F <path> | — | Filter: show only this path and its children |
-n <N> | rows | Show at most N rows |
-O <N> | rows | Skip first N rows |
-E <N> | elements | Skip first N array elements (use with -F) |
-L <N> | elements | Show at most N array elements (use with -F) |
-d <N> | depth | Collapse containers deeper than N levels (object key depth; array indices don't count) |
-E and -L are element-aware and never cut an element in the middle.
Edit flags
-f — Force: apply change to file. Default is preview-only.
- Without
-f: shows a color-annotated diff of the original JSON with ~~~~~ underline markers at the exact change position.
-R / --reveal — Show secret-like values in plain text (default: masked
with prefix***suffix). Place before the subcommand:
jstool.py -R find apiKey config.json -k. On-disk writes via -f always
use the real value regardless of -R.
Path syntax
root root node
count root-level key
users[0] array element
users[0].name nested key
root[0].key root-array element key
Value parsing
Values are parsed as JSON first, then fall back to plain string:
Alice → string
42 → integer
3.14 → number
true / false → boolean
null → null
'{"k":"v"}' → object
'[1,2,3]' → array
@path/to.json → read value from file
Output format (view)
root object
users array
users[0] object
users[0].name string Alice
users[0].age integer 30
users[1].age integer (null) ← magenta: null with inferred type
orphan unknown (null) ← red: null, type unknown
meta object (empty) ← dim: empty container
config object {3 keys} ← dim: collapsed by -d
tags array [12 items] ← dim: collapsed by -d
Colors: cyan = path, yellow = type, green = value,
magenta = inferred null, red = unknown/delete, dim = empty/collapsed.
Workflow
Explore a JSON file
python3 ~/.claude/skills/json-flat-tool/jstool.py view data.json -s
python3 ~/.claude/skills/json-flat-tool/jstool.py view data.json -F "data[0].bids" -E 5 -L 3
python3 ~/.claude/skills/json-flat-tool/jstool.py schema data.json --title "My API"
python3 ~/.claude/skills/json-flat-tool/jstool.py view data.json -d 2
python3 ~/.claude/skills/json-flat-tool/jstool.py view data.json -d 1 -F users
Edit a JSON file
python3 ~/.claude/skills/json-flat-tool/jstool.py set users[0].name Bob data.json
python3 ~/.claude/skills/json-flat-tool/jstool.py set users[0].name Bob data.json -f
python3 ~/.claude/skills/json-flat-tool/jstool.py "users[0].name" = Bob data.json -f
python3 ~/.claude/skills/json-flat-tool/jstool.py before users[1] '{"name":"Eve"}' data.json -f
python3 ~/.claude/skills/json-flat-tool/jstool.py del users[2] data.json -f
python3 ~/.claude/skills/json-flat-tool/jstool.py set-null users[0].age data.json -f
Set value from file (@file)
python3 ~/.claude/skills/json-flat-tool/jstool.py set provider.openai @/tmp/openai.json config.json -f
Clone a subtree (copy)
python3 ~/.claude/skills/json-flat-tool/jstool.py copy \
provider.google.models.antigravity-gemini-3-pro \
provider.google.models.my-new-model \
config.json
python3 ~/.claude/skills/json-flat-tool/jstool.py copy \
provider.google.models.antigravity-gemini-3-pro \
provider.google.models.my-new-model \
config.json -f
Deep-merge a patch file (merge)
python3 ~/.claude/skills/json-flat-tool/jstool.py merge provider.google.models /tmp/new-models.json config.json
python3 ~/.claude/skills/json-flat-tool/jstool.py merge provider.google.models /tmp/new-models.json config.json -f
Find nodes by pattern (find)
python3 ~/.claude/skills/json-flat-tool/jstool.py find apiKey config.json
python3 ~/.claude/skills/json-flat-tool/jstool.py find apiKey config.json -k
python3 ~/.claude/skills/json-flat-tool/jstool.py find "sk-.*" config.json -v
python3 ~/.claude/skills/json-flat-tool/jstool.py find "APIKEY" config.json -k -i
python3 ~/.claude/skills/json-flat-tool/jstool.py find "*api*" config.json -k -g -i
find options:
| Flag | Description |
|---|
-k | Match path only |
-v | Match value only |
-i | Case-insensitive |
-g | Glob mode (fnmatch full-string wildcard instead of regex) |
-k and -v are mutually exclusive. Without either flag, both path and value are searched.
Inline / piped JSON
echo '{"name":"Alice"}' | python3 ~/.claude/skills/json-flat-tool/jstool.py view
curl https://api.example.com/data | python3 ~/.claude/skills/json-flat-tool/jstool.py schema
HTTP API & WebSocket Analysis
This skill should be invoked automatically whenever the user pastes an API response, curl output, WSS frame, or asks to analyze any protocol-level JSON payload.
Analyze an HTTP API response
curl -s https://api.example.com/markets | \
python3 ~/.claude/skills/json-flat-tool/jstool.py view -s
curl -s https://api.example.com/orderbook/BTC-USD | \
python3 ~/.claude/skills/json-flat-tool/jstool.py schema --title "Orderbook API"
curl -s https://api.example.com/events > /tmp/events.json
python3 ~/.claude/skills/json-flat-tool/jstool.py view /tmp/events.json -d 2
curl -s https://api.example.com/data | \
python3 ~/.claude/skills/json-flat-tool/jstool.py find "price" -k
Analyze a WebSocket (WSS) message capture
When a user pastes a raw WebSocket frame payload or a captured .json log:
echo '<paste-wss-frame-here>' > /tmp/wss_frame.json
python3 ~/.claude/skills/json-flat-tool/jstool.py view /tmp/wss_frame.json -s
cat wss_frames/*.json | jq -s '.' | \
python3 ~/.claude/skills/json-flat-tool/jstool.py schema --title "WSS Message Schema"
python3 ~/.claude/skills/json-flat-tool/jstool.py view wss_log.json -F "data" -E 0 -L 5
Orderbook / market-data snapshots
python3 ~/.claude/skills/json-flat-tool/jstool.py view orderbook.json -d 1
python3 ~/.claude/skills/json-flat-tool/jstool.py view orderbook.json -F "bids" -L 10
python3 ~/.claude/skills/json-flat-tool/jstool.py view orderbook.json -F "asks" -L 10
python3 ~/.claude/skills/json-flat-tool/jstool.py schema orderbook.json --title "CLOB Orderbook"
Tips for API / protocol analysis
| Scenario | Recommended command |
|---|
| Unknown response shape | view -s (schema mode) |
| Large paginated list | view -F <array-path> -E <skip> -L <count> |
| Find auth / key fields | find "token|key|secret|auth" -k -i |
| Understand field types | schema --title "<endpoint-name>" |
| Diff two responses | save both → set / merge preview |
| WSS frame with nested events | view -d 2 then view -F <event-path> |
Inline JSON from chat
When the user pastes raw JSON directly into the conversation, save it to a temp file:
cat > /tmp/api_payload.json << 'EOF'
<paste JSON here>
EOF
python3 ~/.claude/skills/json-flat-tool/jstool.py view /tmp/api_payload.json -s
Notes
before / after only apply to array elements, not object keys.
- Adding a brand-new key (
set cannot create missing parents).
set requires every segment of the target path to already exist; otherwise
it errors with KeyError: 'foo'. Two correct ways to add a new key:
merge (recommended): write the patch to a small JSON file and
deep-merge it into an existing parent. merge creates any missing keys
inside the patch, but the target path itself must still exist:
cat > /tmp/patch.json <<'EOF'
{ "tts": { "auto": "tagged", "provider": "xiaomi" } }
EOF
jstool.py merge messages /tmp/patch.json config.json -f
set with @file against an existing parent: when the parent object
already exists, you can drop a whole subtree in:
jstool.py set messages.tts @/tmp/tts-block.json config.json -f
-f without a file path prints modified JSON to stdout.
-E / -L require -F pointing to an array path.
- Array sampling for schema inference: up to 20 elements.
required in schema = fields present and non-empty in all sampled elements.
@file syntax works with set and B-style (=); merge always takes a file path directly.
copy performs a deep clone — mutations to the copy do not affect the source.
merge for non-dict targets replaces the value entirely (patch wins).
find -g uses fnmatch (full-string wildcard): use *api* not api* for substring matching.
find without -g uses Python re.search (substring regex by default).
-d N only affects the flat view display; schema inference and edit commands are unaffected.
-d N depth counts object key traversals only — array indices ([0], [1], …) do not increment depth.
Sensitive-key masking
By default, view / find / set / del previews mask string values whose
key name looks like a secret (apiKey, token, password, client_secret,
private_key, …). Output looks like:
models.providers.newapi.apiKey string sk-P***mywu (masked; -R to reveal)
Pass -R (or --reveal) before the subcommand to see plain text for one
invocation. On-disk writes are never affected by masking — -f always writes
the real value, regardless of -R.
jstool.py find apiKey config.json -k
jstool.py -R find apiKey config.json -k
Configuring masked patterns
Patterns and the prefix***suffix format live in <skill-dir>/config.json,
not hard-coded in jstool.py. Edit that file (with jstool itself, of
course) to add/remove patterns or change how much of the value is shown:
// <skill-dir>/config.json
{
"sensitive_keys": {
"key_name_patterns": ["apikey", "token", "password", "..."],
"mask_format": {
"show_prefix": 4,
"show_suffix": 4,
"min_length_to_mask": 8,
"placeholder": "***"
}
}
}
Matching is case-insensitive and ignores punctuation in the key name —
apiKey, api_key, API-KEY, apikey all hit the same pattern.