Files
moltbot/docs/cli/path.md

13 KiB

summary, read_when, title
summary read_when title
CLI reference for `openclaw path` (inspect and edit workspace files via the `oc://` addressing scheme)
You want to read or write a leaf inside a workspace file from the terminal
You're scripting against workspace state and want a stable, kind-agnostic addressing scheme
You're debugging a `oc://` path (validate the syntax, see what it resolves to)
Path

openclaw path

Plugin-provided shell access to the oc:// addressing substrate: one kind-dispatched path scheme for inspecting and editing addressable workspace files (markdown, jsonc, jsonl). Self-hosters, plugin authors, and editor extensions use it to read, find, or update a narrow location without hand-rolling per-file parsers.

The CLI mirrors the substrate's public verbs:

  • resolve is concrete and single-match.
  • find is the multi-match verb for wildcards, unions, predicates, and positional expansion.
  • set only accepts concrete paths or insertion markers; wildcard patterns are rejected before writing.

path is provided by the bundled optional oc-path plugin. Enable it before first use:

openclaw plugins enable oc-path

Subcommands

Subcommand Purpose
resolve <oc-path> Print the concrete match at the path (or "not found").
find <pattern> Enumerate matches for a wildcard / union / predicate path.
set <oc-path> <value> Write a leaf or insertion target at a concrete path. Supports --dry-run.
validate <oc-path> Parse-only; print structural breakdown (file / section / item / field).
emit <file> Round-trip a file through parseXxx + emitXxx (byte-fidelity diagnostic).

Global flags

Flag Purpose
--cwd <dir> Resolve the file slot against this directory (default: process.cwd()).
--file <path> Override the file slot's resolved path (absolute access).
--json Force JSON output (default when stdout is not a TTY).
--human Force human output (default when stdout is a TTY).
--dry-run (only on set) print the bytes that would be written without writing.

oc:// syntax

oc://FILE/SECTION/ITEM/FIELD?session=SCOPE

Slot rules: field requires item, and item requires section. Across all four slots:

  • Quoted segments"a/b.c" survives / and . separators. Content is byte-literal; " and \ are not allowed inside quotes. The file slot is also quote-aware: oc://"skills/email-drafter"/Tools/$last treats skills/email-drafter as a single file path.
  • Predicates[k=v], [k!=v], [k<v], [k<=v], [k>v], [k>=v]. Numeric ops require both sides to coerce to finite numbers.
  • Unions{a,b,c} matches any of the alternatives.
  • Wildcards* (single sub-segment) and ** (zero-or-more, recursive). find accepts these; resolve and set reject them as ambiguous.
  • Positional$last resolves to the last index / last-declared key.
  • Ordinal#N for Nth match by document order.
  • Insertion markers+, +key, +nnn for keyed / indexed insertion (use with set).
  • Session scope?session=cron-daily etc. Orthogonal to slot nesting. Session values are raw, not percent-decoded; they may not contain control characters or reserved query delimiters (?, &, %).

Reserved characters (?, &, %) outside quoted, predicate, or union segments are rejected. Control characters (U+0000-U+001F, U+007F) are rejected anywhere, including the session query value.

formatOcPath(parseOcPath(path)) === path is guaranteed for canonical paths. Non-canonical query parameters are ignored except for the first non-empty session= value.

Addressing by file kind

Kind Addressing model
Markdown H2 sections by slug, bullet items by slug or #N, frontmatter via [frontmatter].
JSONC/JSON Object keys and array indexes; dots split nested sub-segments unless quoted.
JSONL Top-level line addresses (L1, L2, $last), then JSONC-style descent inside the line.

resolve returns a structured match: root, node, leaf, or insertion-point, with a 1-based line number. Leaf values are surfaced as text plus a leafType so plugin authors can render previews without depending on the per-kind AST shape.

Mutation contract

set writes one concrete target:

  • Markdown frontmatter values and - key: value item fields are string leaves. Markdown insertions append sections, frontmatter keys, or section items and render a canonical markdown shape for the changed file.
  • JSONC leaf writes coerce the string value to the existing leaf type (string, finite number, true/false, or null). JSONC object and array insertions parse <value> as JSON and use the jsonc-parser edit path for ordinary leaf writes, preserving comments and nearby formatting.
  • JSONL leaf writes coerce like JSONC inside a line. Whole-line replacement and append parse <value> as JSON. Rendered JSONL preserves the file's dominant LF/CRLF line-ending convention.

Use --dry-run before user-visible writes when the exact bytes matter. The substrate preserves byte-identical output for parse/emit round-trips, but a mutation can canonicalize the edited region or file depending on kind.

Examples

# Validate a path (no filesystem access)
openclaw path validate 'oc://AGENTS.md/Tools/$last/risk'

# Read a leaf
openclaw path resolve 'oc://gateway.jsonc/version'

# Wildcard search
openclaw path find 'oc://session.jsonl/*/event' --file ./logs/session.jsonl

# Dry-run a write
openclaw path set 'oc://gateway.jsonc/version' '2.0' --dry-run

# Apply the write
openclaw path set 'oc://gateway.jsonc/version' '2.0'

# Byte-fidelity round-trip (diagnostic)
openclaw path emit ./AGENTS.md

More grammar examples:

# Quote keys containing / or .
openclaw path resolve 'oc://config.jsonc/agents.defaults.models/"anthropic/claude-opus-4-7"/alias'

# Predicate search over JSONC children
openclaw path find 'oc://config.jsonc/plugins/[enabled=true]/id'

# Insert into a JSONC array
openclaw path set 'oc://config.jsonc/items/+1' '{"id":"new","enabled":true}' --dry-run

# Insert a JSONC object key
openclaw path set 'oc://config.jsonc/plugins/+github' '{"enabled":true}' --dry-run

# Append a JSONL event
openclaw path set 'oc://session.jsonl/+' '{"event":"checkpoint","ok":true}' --file ./logs/session.jsonl

# Resolve the last JSONL value line
openclaw path resolve 'oc://session.jsonl/$last/event' --file ./logs/session.jsonl

# Address markdown frontmatter
openclaw path resolve 'oc://AGENTS.md/[frontmatter]/name'

# Insert markdown frontmatter
openclaw path set 'oc://AGENTS.md/[frontmatter]/+description' 'Agent instructions' --dry-run

# Find markdown item fields
openclaw path find 'oc://SKILL.md/Tools/*/send_email'

# Validate a session-scoped path
openclaw path validate 'oc://AGENTS.md/Tools/$last/risk?session=cron-daily'

Recipes by file kind

The same five verbs work across kinds; the addressing scheme dispatches on the file extension. The examples below use the fixtures from the PR description.

Markdown

<!-- frontmatter.md -->
---
name: drafter
description: email drafting agent
tier: core
---
## Tools
- gh: GitHub CLI
- curl: HTTP client
- send_email: enabled
$ openclaw path resolve 'oc://x.md/[frontmatter]/tier' --file frontmatter.md --human
leaf @ L4: "core" (string)

$ openclaw path resolve 'oc://x.md/tools/gh/gh' --file frontmatter.md --human
leaf @ L9: "GitHub CLI" (string)

$ openclaw path find 'oc://x.md/tools/*' --file frontmatter.md --human
3 matches for oc://x.md/tools/*:
  oc://x.md/tools/gh           →  node @ L9 [md-item]
  oc://x.md/tools/curl         →  node @ L10 [md-item]
  oc://x.md/tools/send-email   →  node @ L11 [md-item]

The [frontmatter] predicate addresses the YAML frontmatter block; tools matches the ## Tools heading via slug, and item leaves keep their slug form even when the source uses underscores (send_emailsend-email).

JSONC

// config.jsonc
{
  "plugins": {
    "github": {"enabled": true, "role": "vcs"},
    "slack":  {"enabled": false, "role": "chat"}
  }
}
$ openclaw path resolve 'oc://config.jsonc/plugins/github/enabled' --file config.jsonc --human
leaf @ L4: "true" (boolean)

$ openclaw path set 'oc://config.jsonc/plugins/slack/enabled' 'true' --file config.jsonc --dry-run
--dry-run: would write 142 bytes to /…/config.jsonc
{
  "plugins": {
    "github": {"enabled": true, "role": "vcs"},
    "slack":  {"enabled": true, "role": "chat"}
  }
}

JSONC edits go through jsonc-parser, so comments and whitespace survive a set. Run with --dry-run first to inspect the bytes before committing.

JSONL

{"event":"start","userId":"u1","ts":1}
{"event":"action","userId":"u1","ts":2}
{"event":"end","userId":"u1","ts":3}
$ openclaw path find 'oc://session.jsonl/[event=action]/userId' --file session.jsonl --human
1 match for oc://session.jsonl/[event=action]/userId:
  oc://session.jsonl/L2/userId  →  leaf @ L2: "u1" (string)

$ openclaw path resolve 'oc://session.jsonl/L2/ts' --file session.jsonl --human
leaf @ L2: "2" (number)

Each line is a record. Address by predicate ([event=action]) when you do not know the line number, or by the canonical LN segment when you do.

Subcommand reference

resolve <oc-path>

Read a single leaf or node. Wildcards are rejected — use find for those. Exits 0 on a match, 1 on a clean miss, 2 on a parse error or refused pattern.

openclaw path resolve 'oc://AGENTS.md/tools/gh/risk' --human
openclaw path resolve 'oc://gateway.jsonc/server/port' --json

find <pattern>

Enumerate every match for a wildcard / predicate / union pattern. Exits 0 on at least one match, 1 on zero. File-slot wildcards are rejected with OC_PATH_FILE_WILDCARD_UNSUPPORTED — pass a concrete file (multi-file globbing is a follow-up feature).

openclaw path find 'oc://AGENTS.md/tools/**/risk'
openclaw path find 'oc://session.jsonl/[event=action]/userId'
openclaw path find 'oc://config.jsonc/plugins/{github,slack}/enabled'

set <oc-path> <value>

Write a leaf. Pair with --dry-run to preview the bytes that would be written without touching the file. Exits 0 on a successful write, 1 if the substrate refuses (for example, a sentinel guard hit), 2 on parse errors.

openclaw path set 'oc://gateway.jsonc/version' '2.0' --dry-run
openclaw path set 'oc://gateway.jsonc/version' '2.0'
openclaw path set 'oc://AGENTS.md/Tools/+gh/risk' 'low'

The +key insertion marker creates the named child if it does not already exist; +nnn and bare + work for indexed and append insertion respectively.

validate <oc-path>

Parse-only check. No filesystem access. Useful when you want to confirm a template path is well-formed before substituting variables, or when you want the structural breakdown for debugging:

$ openclaw path validate 'oc://AGENTS.md/tools/gh' --human
valid: oc://AGENTS.md/tools/gh
  file:    AGENTS.md
  section: tools
  item:    gh

Exits 0 when valid, 1 when invalid (with a structured code and message), 2 on argument errors.

emit <file>

Round-trip a file through the per-kind parser and emitter. The output should be byte-identical to the input on a sound file — divergence indicates a parser bug or a sentinel hit. Useful for debugging substrate behavior on real-world inputs.

openclaw path emit ./AGENTS.md
openclaw path emit ./gateway.jsonc --json

Exit codes

Code Meaning
0 Success. (resolve / find: at least one match. set: write succeeded.)
1 No match, or set rejected by the substrate (no system-level error).
2 Argument or parse error.

Output mode

openclaw path is TTY-aware: human-readable output on a terminal, JSON when stdout is piped or redirected. --json and --human override the auto-detection.

Notes

  • set writes bytes through the substrate's emit path, which applies the redaction-sentinel guard automatically. A leaf carrying __OPENCLAW_REDACTED__ (verbatim or as a substring) is refused at write time.
  • JSONC parsing and leaf edits use the plugin-local jsonc-parser dependency, so comments and formatting are preserved on ordinary leaf writes instead of going through a hand-rolled parser/re-render path.
  • path does not know about LKG. If the file is LKG-tracked, the next observe call decides whether to promote / recover. set --batch for atomic multi-set through the LKG promote/recover lifecycle is planned alongside the LKG-recovery substrate.