lantern

cmd-fence

CLI Command: fence

API: GET /api/oculus/fences, POST /api/oculus/fences/{id}/execute Files: oculus/api.py, oculus/fence_index_builder.py

Query and execute code fences directly via MCP or API.

Fence Labeling

Fences can be given labels for easy addressing. Add the label in square brackets after the fence language:

```python[my-label]
# This fence can be addressed as 'my-label' or 'node-slug:my-label'
print("Hello")
```

Label syntax examples:

  • python[fetch] - label is "fetch"
  • python[jenkins-job-config] - label is "jenkins-job-config"
  • yaml[config,execute=true] - label is "config" with additional attributes

Fence Addressing Formats

The oculus_execute_fence tool supports multiple addressing formats:

Format Example Description
Label jenkins-job-config Direct label lookup (globally unique)
slug:label my-node:fetch Node slug + fence label
slug:index my-node:0 Node slug + fence position (0-indexed)
fence-id my-node-fence-0 Direct fence ID from index

Usage

Usage

CLI: Smart Fence Discovery

The fence command provides pattern-matching discovery and optional auto-execution:

# List all fences (table format)
oculus fence

# Pattern matching
oculus fence theme              # Substring match
oculus fence ^magic             # Prefix match (starts with)
oculus fence ball$              # Suffix match (ends with)
oculus fence daily.todo         # Dotted path (node.fence)

# Filter by attributes
oculus fence --type python      # Filter by fence type
oculus fence --node my-node     # Filter by node
oculus fence --tags aws,infra   # Filter by tags

# Auto-execute if single match
oculus fence themes -x          # Execute if unique match

# Show detailed info (params, location)
oculus fence magic-8-ball -i    # Show fence info + params

# JSON output for scripting
oculus fence --json | jq '.fences[].label'

CLI: Fence Execution

# Execute by label
oculus exec magic-8-ball --question "Deploy?"

# Execute by slug:index
oculus exec my-node:0

# Execute with JSON params
oculus execute my-fence --params '{"key": "value"}'

# Output format control
oculus exec themes -f yaml      # yaml, json, markdown, text, raw

# Force execution (ignore execute bit)
oculus exec my-fence --force

When a fence requires parameters and you don't provide them, the CLI prompts interactively:

$ oculus exec magic-8-ball
Fence 'magic-8-ball' requires parameters
Enter required parameters:
  question (Your yes/no question): Should I deploy?

Params as Config

When executing a fence via MCP/API, the params object is passed to Python fences as the config variable:

# params: {"name": "Alice", "count": 5}
# These are accessible via config:
name = config.get('name', 'default')
count = config.get('count', 1)
result = f"Hello {name}! Count: {count}"

Fence Index

The fence index is automatically updated when:

  • A node is modified via poke
  • A fence's metadata is updated via oculus_update_fence

Index location: ~/.local/share/oculus/.cache/fence-index/fence-index.json

Params API

Get parameter documentation for a fence tool:

# Get params for a labeled fence
curl "http://localhost:7778/api/oculus/fences/magic-8-ball/params"

Returns:

{
  "fence_id": "magic-8-ball",
  "fence_label": "magic-8-ball", 
  "slug": "how-to-create-oculus-tool",
  "params": [
    {
      "name": "question",
      "type": "string",
      "description": "Your yes/no question",
      "default": null,
      "optional": false
    }
  ],
  "required": ["question"],
  "optional": [],
  "example_call": {
    "fence_id": "magic-8-ball",
    "params": {"question": "<string>"}
  }
}

Parameters are extracted from:

  • PARAMS: section in fence docstrings
  • ## config yaml sections (defaults)

See fence-params for the self-documenting fence pattern.

Test Suite

TableConfig:
  array_path: tests
  columns:
    Test: test
    Category: category
    Status: status
    Details: details
  format: markdown

❌ Fence Execution Error: "'fence-test-data' - Down the rabbit hole we went, but that node doesn't exist! Try 'oculus list' to see what's available."

Unit Tests

Test Command Expected
Execute by label oculus_execute_fence({fence_id: "label"}) Finds fence by label
Execute by slug:label oculus_execute_fence({fence_id: "node:label"}) Finds fence in specific node
Execute by slug:index oculus_execute_fence({fence_id: "node:0"}) Finds first fence in node
Pass params oculus_execute_fence({params: {k: v}}) Params available as config
Force execute oculus_execute_fence({force: true}) Overrides execute=false bit
Index rebuild on poke oculus_poke(...) Fence index updated

Automatic Fence ID Injection βœ… AUTO-FIXED

Status: Implemented in ast_converter.py:205-245 Pattern: gentle-guidance

When rendering markdown, Oculus automatically injects [id=computed-id] into unlabeled fences. This enables any client to address fences by simply parsing the fence info line.

Before (raw file)

```yaml
location: entrance
```

After (rendered output)

```yaml[id=adventure-game-data-fence-0]
location: entrance
```

How It Works

  • During AST-to-markdown conversion, we track current section and fence count
  • For unlabeled fences, compute ID using same formula as fence index: {slug}-{section-slug}-fence-{idx}
  • Inject [id=...] into the fence info line
  • Labeled fences keep their labels unchanged

Client Integration

Any client (neovim, web, CLI) can:

  • Find fence at cursor position
  • Parse the fence info line using FenceURLParser
  • Get parsed.label (which falls back to metadata.id)
  • Call execute with that ID

Competent computing says: "Parse the fence info, get the ID." Gentle guidance says: "We inject the ID for you. No line number matching required."

Slots

North

slots:
- oculus-cli

South

slots:
- fence-test-data

East

slots:
- context:
  - Linking fence params API docs to core fence docs
  slug: fence-params
- context:
  - Linking fence ID injection docs to gentle guidance pattern
  slug: gentle-guidance
↑ northoculus-cli
↓ southfence-test-data
β†’ eastfence-paramsgentle-guidance