refactor: moved org-agent to its own repository as a submodule

This commit is contained in:
2026-03-27 15:46:53 -04:00
parent 01f76a4570
commit b7e082c403
176 changed files with 19686 additions and 9665 deletions

View File

@@ -0,0 +1,74 @@
---
name: org-json-bridge
description: "Provides a bridge between Org-mode files and JSON for programmatic manipulation. Use when: needing to parse Org-mode to JSON, serialize JSON to Org-mode, or modify Org-mode files programmatically. NOT for: simple text edits, general Org-mode viewing, or direct human authoring."
homepage: https://docs.openclaw.ai/tools/skills/org-json-bridge
metadata: { "openclaw": { "emoji": "🌉", "requires": { "bins": ["python3", "pip", "emacs"], "env": [] }, "primaryEnv": "" } }
---
# Org-JSON Bridge
This skill develops and utilizes an external tool to convert Org-mode files into a structured JSON representation, and vice-versa. This enables robust programmatic modification of Org-mode documents, addressing the limitations of direct string manipulation via the `edit` tool for complex structures like tables or source blocks. By working with a structured data model, it ensures consistent formatting and reliable updates.
## When to Use
**USE this skill when:**
- Modifying Org-mode file content programmatically (e.g., adding/removing table rows, updating flags in structured blocks).
- Converting Org-mode documents to a JSON representation for data processing.
- Converting structured JSON data back into Org-mode format.
- Encountering difficulties with the `edit` tool due to complex Org-mode formatting (e.g., tables, source blocks).
**DON'T use this skill when:**
- Performing simple, single-line text edits in Org-mode files.
- Viewing or rendering Org-mode content (use a dedicated Org-mode client).
- Authoring Org-mode documents directly.
- Editing existing skills (use `edit` tool directly for SKILL.md files).
## Instructions
This skill provides a Python script (`org_bridge.py`) that acts as a command-line interface to the Org-mode to JSON bridge.
1. **Parse Org-mode to JSON (Command Line):**
```bash
org_bridge.py parse --file-path "path/to/my-doc.org" > output.json
```
2. **Modify JSON (Internal Agent Logic):**
* The agent would then perform internal Python logic to load `output.json`, modify the JSON object (AST), and save the modified JSON to a new file, e.g., `modified_data.json`.
3. **Render JSON to Org-mode (Command Line):**
```bash
org_bridge.py render --json-input-file "path/to/modified_data.json" --output-file "path/to/new-doc.org"
```
## Commands
### `org_bridge.py parse`
Parse an Org-mode file into a JSON representation and print to stdout.
```bash
# Example: Parse an Org-mode file to JSON
exec ~/.openclaw/workspace/skills/org-json-bridge/org_bridge.py parse --file-path "/home/amr/.openclaw/workspace/memex/5_projects/agora/agora-requirements-01-overview.org"
```
### `org_bridge.py render`
Render a JSON representation back into an Org-mode file.
```bash
# Example: Render JSON back to an Org-mode file
# Assume 'modified_data.json' exists with the desired AST
exec ~/.openclaw/workspace/skills/org-json-bridge/org_bridge.py render --json-input-file "/path/to/modified_data.json" --output-file "/home/amr/.openclaw/workspace/memex/5_projects/agora/agora-requirements-01-overview.org"
```
## Configuration
Environment variables needed:
- None directly for the bridge, but the underlying parser might have config.
Config values in `openclaw.json`:
- `skill.org-json-bridge.parser_path` - Path to the Python/Node.js script (if not directly in skill folder).
## Notes
- Initial implementation will focus on core Org-mode elements needed for requirements documents (headings, lists, tables, source blocks).
- Requires installation of an Org-mode parsing library (e.g., `orgparse` for Python), though the Emacs Lisp handles the heavy lifting here.
- This skill is a foundational piece to enhance reliable document manipulation capabilities.

View File

@@ -0,0 +1,60 @@
;;; org-json-bridge.el --- Bridge for LLM agents to manipulate Org-mode via JSON
(require 'org-element)
(require 'json)
(require 'cl-lib)
(defun org-json-bridge--clean-tree (element)
"Recursively convert an Org ELEMENT into a JSON-serializable format."
(cond
((listp element)
(let* ((type (car element))
(props (nth 1 element))
(children (nthcdr 2 element))
(cleaned-props nil))
(cl-loop for (key val) on props by 'cddr do
(unless (member key '(:standard-properties :parent))
(let ((json-key (substring (symbol-name key) 1)))
(push (cons json-key
(cond
((stringp val) val)
((numberp val) val)
((booleanp val) val)
(t (format "%s" val))))
cleaned-props))))
(list (cons 'type (symbol-name type))
(cons 'properties cleaned-props)
(cons 'contents (mapcar #'org-json-bridge--clean-tree children)))))
((stringp element) element)
(t (format "%s" element))))
(defun org-to-json (file-path)
"Parse an Org file and output its structure as JSON."
(with-current-buffer (find-file-noselect file-path)
(let* ((tree (org-element-parse-buffer))
(cleaned (org-json-bridge--clean-tree tree)))
(princ (json-encode cleaned)))))
(defun json-to-org (json-string output-file)
"Take a JSON representation of an Org tree and write it back to a file."
(let ((data (json-read-from-string json-string)))
(with-temp-file output-file
(insert (org-element-interpret-data data)))))
;; Entry point for batch mode
(message "DEBUG: Entry point reached")
;; Sometimes -- is left in command-line-args-left
(when (string= (car command-line-args-left) "--")
(pop command-line-args-left))
(let ((command (pop command-line-args-left)))
(message "DEBUG: Command is %s" command)
(cond
((string= command "org-to-json")
(let ((file (pop command-line-args-left)))
(org-to-json file)))
((string= command "json-to-org")
(let ((json-str (pop command-line-args-left))
(out-file (pop command-line-args-left)))
(json-to-org json-str out-file)))))

View File

@@ -0,0 +1,54 @@
import subprocess
import json
import os
import argparse
from typing import Dict, Any, Optional
class OrgBridge:
def __init__(self, lisp_script_path: str = os.path.join(os.path.dirname(__file__), "org-json-bridge.el")):
self.lisp_path = os.path.abspath(lisp_script_path)
def _run_emacs_batch(self, command: str, *args) -> str:
"""Helper to execute the Emacs batch command with arguments."""
cmd = [
"emacs", "--batch",
"-l", self.lisp_path,
"--", command, *args
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return result.stdout.strip()
def parse_to_dict(self, file_path: str) -> Dict[str, Any]:
"""Reads an Org file and returns its AST as a Python Dictionary."""
abs_path = os.path.abspath(file_path)
json_output = self._run_emacs_batch("org-to-json", abs_path)
return json.loads(json_output)
def write_from_dict(self, ast_dict: Dict[str, Any], output_path: str):
"""Takes a Python Dictionary (AST) and writes it back to an Org file."""
json_input = json.dumps(ast_dict)
abs_output_path = os.path.abspath(output_path)
self._run_emacs_batch("json-to-org", json_input, abs_output_path)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Org-mode to JSON bridge for programmatic manipulation.")
parser.add_argument("action", choices=["parse", "render"], help="Action to perform: 'parse' an Org file to JSON, or 'render' JSON to an Org file.")
parser.add_argument("--file-path", help="Path to the Org-mode file (required for 'parse' action).")
parser.add_argument("--json-input-file", help="Path to a JSON file containing the AST (required for 'render' action).")
parser.add_argument("--output-file", help="Path to output the Org-mode file (required for 'render' action).")
args = parser.parse_args()
bridge = OrgBridge()
if args.action == "parse":
if not args.file_path:
parser.error("--file-path is required for the 'parse' action.")
org_ast = bridge.parse_to_dict(args.file_path)
print(json.dumps(org_ast, indent=2))
elif args.action == "render":
if not args.json_input_file or not args.output_file:
parser.error("--json-input-file and --output-file are required for the 'render' action.")
with open(args.json_input_file, 'r') as f:
ast_dict = json.load(f)
bridge.write_from_dict(ast_dict, args.output_file)