DOC5B_OPENCLAW1_ACCOUNT_BUILD.md
Current Specs/DOC5/DOC5B_OPENCLAW1_ACCOUNT_BUILD.md
# DOC5B — File Sync Skill Build (openclaw1 account)
**Date:** 2026-02-25
**Status:** Implementation guide — for Codex / Claude Code on the `openclaw1` user account.
**Source:** DOC5 File Sync & Security Architecture v1.11.7 (§7)
**Companion:** DOC5A — Infrastructure Build (will account)
**Architecture:** Reconciled to DOC3 R6 — SKILL.md + scripts via exec (not skill.json/index.js)
---
## What This Document Covers
Everything that runs under the `openclaw1` macOS user account:
- The `file-sync` OpenClaw skill (SKILL.md + helper scripts)
- Verification that openclaw1 can read/write the expected paths
- Updates to existing app skills (Word, Legal Tables) to add snapshot workflow
**What this does NOT cover:** Infrastructure, sync scripts, dashboard, launchd jobs, or anything owned by the `will` account. That's DOC5A, and it must be completed first.
---
## Prerequisites
- DOC5A has been completed and tested on the `will` account
- Logged into the `openclaw1` macOS user account
- OpenClaw is installed and running
- The following paths exist and have correct permissions:
- `~/onedrive-mirror/` — readable (will-owned)
- `~/onedrive-outbox/` — read/write (openclaw1-owned)
- `~/working/` — read/write (openclaw1-owned)
- `~/snapshots/` — read/write (openclaw1-owned)
- `~/sync-config/config.json` — readable
- `~/sync-config/requests.txt` — writable
- `~/sync-config/logs/` — readable
- `~/sync-config/firm-folders.txt` — readable
- `~/sync-config/personal-folders.txt` — readable
---
## Step 0: Verify Access
Before building anything, confirm the permission boundaries work:
```bash
# Should SUCCEED:
cat ~/sync-config/config.json
ls ~/onedrive-mirror/firm/
touch ~/onedrive-outbox/firm/test-write.txt && rm ~/onedrive-outbox/firm/test-write.txt
echo "TEST" >> ~/sync-config/requests.txt
touch ~/working/test.txt && rm ~/working/test.txt
touch ~/snapshots/firm/test.txt && rm ~/snapshots/firm/test.txt
cat ~/sync-config/logs/firm-mirror.log
cat ~/sync-config/firm-folders.txt
# Should FAIL (permission denied):
touch ~/onedrive-mirror/firm/test.txt
echo "x" >> ~/sync-config/firm-folders.txt
echo "x" >> ~/sync-config/config.json
ls ~/sync-config/scripts/
```
If any "should succeed" fails or any "should fail" succeeds, go back to DOC5A and fix permissions before proceeding.
---
## Step 1: Create the file-sync Skill
### Skill Architecture (R6 Pattern)
Per DOC3 R6, OpenClaw skills are:
- **SKILL.md** — Markdown with YAML frontmatter (instructions) + body (how to use)
- **scripts/** — Helper scripts called via the `exec` tool
- **knowledge/** — Reference docs (optional)
There is no `skill.json` or `index.js`. The agent reads SKILL.md, understands the available scripts, and calls them via `exec`.
### Folder Structure
```
~/.openclaw/workspace/skills/file-sync/
├── SKILL.md
└── scripts/
├── sync-status.sh
├── list-folders.sh
├── list-mirror.sh
├── request-folder.sh
├── list-outbox.sh
├── list-snapshots.sh
├── read-log.sh
└── create-snapshot.sh
```
### SKILL.md
Create `~/.openclaw/workspace/skills/file-sync/SKILL.md`:
```markdown
---
name: file-sync
description: >
Read-only awareness of the OneDrive file sync system. Check sync status,
read logs, find files in the mirror, list outbox contents, request folder
access, and create pre-edit snapshots. Cannot modify sync configuration.
allowed-tools:
- exec
- read
- write
requirements:
binaries:
- rsync
- python3
env: []
os: darwin
---
# File Sync — OneDrive Mirror Awareness
The user's work files are synced from OneDrive to a local read-only mirror.
You read files from the mirror, work in a scratch directory, and save
finished work to the outbox. A background process (running on the `will`
account) pushes outbox files to OneDrive automatically.
## File Locations
| Path | Access | Purpose |
|------|--------|---------|
| `~/onedrive-mirror/firm/` | Read only | Mirror of firm OneDrive folders |
| `~/onedrive-mirror/personal/` | Read only | Mirror of personal OneDrive folders |
| `~/onedrive-outbox/firm/` | Read/write | Save finished work here → pushes to firm OneDrive |
| `~/onedrive-outbox/personal/` | Read/write | Save finished work here → pushes to personal OneDrive |
| `~/working/` | Read/write | Scratch space (never synced) |
| `~/snapshots/` | Read/write | Pre-edit backups |
## Workflow for Editing Files
**Follow this exact sequence every time you modify a file:**
1. **Find** the source file in the mirror (read-only)
2. **Snapshot** the original BEFORE any edit (MANDATORY — see snapshot script below)
3. **Copy** to `~/working/` for editing
4. **Edit** the working copy
5. **Save** the finished result to the outbox, preserving the relative path
6. **Tell** the user the file is in the outbox and will push to OneDrive
Example:
```
Source: ~/onedrive-mirror/firm/Henderson/Henderson_MTD_v3.docx
Snapshot: ~/snapshots/firm/Henderson/Henderson_MTD_v3_20260224-153022.docx
Working: ~/working/Henderson_MTD_v3.docx
Output: ~/onedrive-outbox/firm/Henderson/Henderson_MTD_v4.docx
```
## CRITICAL: Pre-Work Snapshot
BEFORE modifying any document from the mirror, you MUST create a snapshot:
```bash
exec scripts/create-snapshot.sh firm Henderson/Henderson_MTD_v3.docx
```
This copies the original to `~/snapshots/firm/Henderson/Henderson_MTD_v3_{timestamp}.docx`.
**NEVER skip this step.** If the snapshot script fails, do not proceed with the edit.
Snapshots are not needed for files you create from scratch (no original to back up).
## File Naming
The outbox push has naming rules configured by the user. Common patterns:
- User says "save as v4" → use `Henderson_MTD_v4.docx`
- User doesn't specify → use a descriptive name that won't conflict
(e.g., `Henderson_MTD_with_TOA.docx`)
- If a naming conflict blocks the push, the user resolves it in their dashboard
When in doubt, ask the user what to name the output file.
## Folder Access
You can only read folders the user has synced. Check what's available:
```bash
exec scripts/list-folders.sh firm
```
If you need a folder that isn't synced:
```bash
exec scripts/request-folder.sh firm "Patel/" "Need to review the complaint for TOA generation"
```
The request appears in the user's sync dashboard. You cannot add folders yourself.
## Available Scripts
| Script | Purpose | Arguments |
|--------|---------|-----------|
| `scripts/sync-status.sh` | Overall sync status | (none) |
| `scripts/list-folders.sh` | Synced folders for an account | `<account>` (firm\|personal) |
| `scripts/list-mirror.sh` | Browse mirror contents | `<account>` [subpath] |
| `scripts/request-folder.sh` | Request folder access | `<account> <folder> <reason>` |
| `scripts/list-outbox.sh` | Outbox contents | `<account>` |
| `scripts/list-snapshots.sh` | Available snapshots | `<account>` [folder] |
| `scripts/read-log.sh` | Recent sync log lines | `<job>` [lines] |
| `scripts/create-snapshot.sh` | Snapshot before editing | `<account> <relative-path>` |
## What You CANNOT Do
These restrictions exist to protect attorney-client privileged files:
- Modify sync configuration (folder lists, timing, naming rules)
- Pause or resume sync
- Access the sync dashboard (requires a password you don't have)
- Access OneDrive folders not in the sync list
- Delete files from OneDrive (no mechanism exists)
Do not attempt to work around these restrictions.
## Conversation Examples
> **User:** "Elnor, add a TOA to the Henderson MTD."
> *Read from mirror → snapshot original → work in scratch → save to outbox*
> "Done. I've saved Henderson_MTD_v4.docx with the TOA to your firm outbox.
> It'll push to OneDrive in the Henderson folder within a few minutes. I
> also have a snapshot of the original v3 in case you need to revert."
> **User:** "Elnor, the Patel case folder isn't in your sync."
> *Call request-folder.sh*
> "I've requested access to the Patel folder on your firm OneDrive. You'll
> see the request in your sync dashboard — just hit Approve and I'll have
> the files within a few minutes."
> **User:** "Elnor, that TOA messed up the formatting. Go back to the original."
> *Read snapshot list → find pre-edit snapshot → copy to outbox*
> "I have a snapshot of the original from before I added the TOA. I've
> copied it to the outbox so it pushes back to OneDrive."
```
---
## Step 2: Create Helper Scripts
All scripts go in `~/.openclaw/workspace/skills/file-sync/scripts/`.
### sync-status.sh
```bash
#!/bin/bash
# Returns overall sync system status
# Usage: sync-status.sh
CONFIG="/Users/openclaw1/sync-config/config.json"
echo "=== FILE SYNC STATUS ==="
echo ""
for ACCOUNT in firm personal; do
LABEL=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['label'])
")
MIRROR_ACTIVE=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['mirror_active'])
")
OUTBOX_ACTIVE=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['outbox_active'])
")
MIRROR_INTERVAL=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['mirror_interval_seconds'])
")
# Last mirror sync time
LOG="/Users/openclaw1/sync-config/logs/${ACCOUNT}-mirror.log"
LAST_SYNC="never"
if [ -f "$LOG" ]; then
LAST_LINE=$(grep "complete" "$LOG" | tail -1)
if [ -n "$LAST_LINE" ]; then
LAST_SYNC=$(echo "$LAST_LINE" | cut -d: -f1-3)
fi
fi
# Mirror file count
MIRROR_PATH=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['mirror_path'])
")
MIRROR_COUNT=$(find "$MIRROR_PATH" -type f 2>/dev/null | wc -l | tr -d ' ')
# Outbox counts
OUTBOX_PATH=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['outbox_path'])
")
PENDING=$(find "$OUTBOX_PATH" -type f -not -name "*.pushed" 2>/dev/null | wc -l | tr -d ' ')
PUSHED=$(find "$OUTBOX_PATH" -name "*.pushed" 2>/dev/null | wc -l | tr -d ' ')
echo "--- $LABEL ---"
echo " Mirror: $([ "$MIRROR_ACTIVE" = "True" ] && echo "Active" || echo "PAUSED") | Last sync: $LAST_SYNC | Every ${MIRROR_INTERVAL}s"
echo " Mirror files: $MIRROR_COUNT"
echo " Outbox: $([ "$OUTBOX_ACTIVE" = "True" ] && echo "Active" || echo "PAUSED") | Pending: $PENDING | Pushed: $PUSHED"
echo ""
done
# Pending requests
REQUESTS="/Users/openclaw1/sync-config/requests.txt"
REQ_COUNT=0
if [ -f "$REQUESTS" ] && [ -s "$REQUESTS" ]; then
REQ_COUNT=$(wc -l < "$REQUESTS" | tr -d ' ')
fi
echo "Pending folder requests: $REQ_COUNT"
```
### list-folders.sh
```bash
#!/bin/bash
# List synced folders for an account
# Usage: list-folders.sh <firm|personal>
ACCOUNT="${1:?Usage: list-folders.sh <firm|personal>}"
INCLUDE_FILE="/Users/openclaw1/sync-config/${ACCOUNT}-folders.txt"
if [ ! -f "$INCLUDE_FILE" ]; then
echo "ERROR: No include file for account '$ACCOUNT'"
exit 1
fi
echo "Synced folders for $ACCOUNT:"
grep '^+ ' "$INCLUDE_FILE" | grep '/$' | sed 's/^+ / /'
```
### list-mirror.sh
```bash
#!/bin/bash
# Browse mirror contents
# Usage: list-mirror.sh <firm|personal> [subpath]
ACCOUNT="${1:?Usage: list-mirror.sh <firm|personal> [subpath]}"
SUBPATH="${2:-}"
CONFIG="/Users/openclaw1/sync-config/config.json"
MIRROR_PATH=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['mirror_path'])
")
SEARCH_PATH="${MIRROR_PATH}${SUBPATH}"
if [ ! -d "$SEARCH_PATH" ]; then
echo "ERROR: Path does not exist: $SEARCH_PATH"
exit 1
fi
echo "Contents of mirror/$ACCOUNT/$SUBPATH:"
ls -lah "$SEARCH_PATH"
```
### request-folder.sh
```bash
#!/bin/bash
# Request folder access from the user
# Usage: request-folder.sh <firm|personal> <folder-name> <reason>
ACCOUNT="${1:?Usage: request-folder.sh <firm|personal> <folder> <reason>}"
FOLDER="${2:?Missing folder name}"
REASON="${3:?Missing reason}"
REQUESTS="/Users/openclaw1/sync-config/requests.txt"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "REQUEST: Add $ACCOUNT folder \"$FOLDER\" — Reason: $REASON — $TIMESTAMP" >> "$REQUESTS"
echo "Request submitted."
echo "The user will see this in their sync dashboard and can approve or deny it."
echo "Once approved, the folder will appear in your mirror on the next sync cycle."
```
### list-outbox.sh
```bash
#!/bin/bash
# List outbox contents for an account
# Usage: list-outbox.sh <firm|personal>
ACCOUNT="${1:?Usage: list-outbox.sh <firm|personal>}"
CONFIG="/Users/openclaw1/sync-config/config.json"
OUTBOX_PATH=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['outbox_path'])
")
echo "=== OUTBOX: $ACCOUNT ==="
echo ""
# Pending (not yet pushed)
echo "--- Pending ---"
find "$OUTBOX_PATH" -type f -not -name "*.pushed" | while read -r F; do
if [ ! -f "${F}.pushed" ]; then
REL="${F#$OUTBOX_PATH}"
SIZE=$(du -h "$F" | cut -f1)
MOD=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$F")
echo " 📄 $REL ($SIZE, saved $MOD)"
fi
done
echo ""
echo "--- Recently Pushed ---"
find "$OUTBOX_PATH" -name "*.pushed" | while read -r MARKER; do
ORIGINAL="${MARKER%.pushed}"
REL="${ORIGINAL#$OUTBOX_PATH}"
PUSH_TIME=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$MARKER")
echo " ✅ $REL (pushed $PUSH_TIME)"
done
```
### list-snapshots.sh
```bash
#!/bin/bash
# List available snapshots
# Usage: list-snapshots.sh <firm|personal> [folder]
ACCOUNT="${1:?Usage: list-snapshots.sh <firm|personal> [folder]}"
FOLDER="${2:-}"
SNAPSHOT_DIR="/Users/openclaw1/snapshots/${ACCOUNT}"
SEARCH_PATH="${SNAPSHOT_DIR}"
if [ -n "$FOLDER" ]; then
SEARCH_PATH="${SNAPSHOT_DIR}/${FOLDER}"
fi
if [ ! -d "$SEARCH_PATH" ]; then
echo "No snapshots found for $ACCOUNT${FOLDER:+/$FOLDER}"
exit 0
fi
echo "=== SNAPSHOTS: $ACCOUNT${FOLDER:+/$FOLDER} ==="
find "$SEARCH_PATH" -type f | sort -r | while read -r F; do
REL="${F#$SNAPSHOT_DIR/}"
SIZE=$(du -h "$F" | cut -f1)
CREATED=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$F")
echo " 📸 $REL ($SIZE, snapped $CREATED)"
done
```
### read-log.sh
```bash
#!/bin/bash
# Read recent sync log lines
# Usage: read-log.sh <job-name> [lines]
# Jobs: firm-mirror, firm-outbox, personal-mirror, personal-outbox
JOB="${1:?Usage: read-log.sh <firm-mirror|firm-outbox|personal-mirror|personal-outbox> [lines]}"
LINES="${2:-50}"
LOG="/Users/openclaw1/sync-config/logs/${JOB}.log"
if [ ! -f "$LOG" ]; then
echo "No log found for job: $JOB"
exit 1
fi
echo "=== Last $LINES lines of $JOB log ==="
tail -n "$LINES" "$LOG"
```
### create-snapshot.sh
```bash
#!/bin/bash
# Create a pre-edit snapshot of a mirror file
# Usage: create-snapshot.sh <firm|personal> <relative-path>
# Example: create-snapshot.sh firm Henderson/Henderson_MTD_v3.docx
ACCOUNT="${1:?Usage: create-snapshot.sh <firm|personal> <relative-path>}"
REL_PATH="${2:?Missing relative path to file}"
CONFIG="/Users/openclaw1/sync-config/config.json"
MIRROR_PATH=$(python3 -c "
import json
config = json.load(open('$CONFIG'))
print(config['accounts']['$ACCOUNT']['mirror_path'])
")
SOURCE="${MIRROR_PATH}${REL_PATH}"
if [ ! -f "$SOURCE" ]; then
echo "ERROR: Source file does not exist: $SOURCE"
exit 1
fi
# Build snapshot path: ~/snapshots/<account>/<relative-dir>/<filename>_<timestamp>.<ext>
SNAPSHOT_DIR="/Users/openclaw1/snapshots/${ACCOUNT}/$(dirname "$REL_PATH")"
FILENAME=$(basename "$REL_PATH")
BASENAME="${FILENAME%.*}"
EXT="${FILENAME##*.}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
SNAPSHOT_FILE="${SNAPSHOT_DIR}/${BASENAME}_${TIMESTAMP}.${EXT}"
mkdir -p "$SNAPSHOT_DIR"
cp -p "$SOURCE" "$SNAPSHOT_FILE"
if [ $? -eq 0 ]; then
echo "Snapshot created: $SNAPSHOT_FILE"
echo "Original size: $(du -h "$SOURCE" | cut -f1)"
echo "Snapshot size: $(du -h "$SNAPSHOT_FILE" | cut -f1)"
else
echo "ERROR: Failed to create snapshot"
exit 1
fi
```
### Make All Scripts Executable
```bash
chmod +x ~/.openclaw/workspace/skills/file-sync/scripts/*.sh
```
---
## Step 3: Update Existing App Skills
Any SKILL.md for skills that modify files from the mirror needs the snapshot instruction added. Add this block to the **Word skill** and **Legal Tables skill** SKILL.md files:
```markdown
## CRITICAL: Pre-Work Snapshot (OneDrive Files)
When editing any file that comes from `~/onedrive-mirror/`:
1. BEFORE any modification, run:
```
exec ~/.openclaw/workspace/skills/file-sync/scripts/create-snapshot.sh <account> <relative-path>
```
2. If the snapshot fails, STOP. Do not proceed with the edit.
3. Copy the file to `~/working/` for editing.
4. Save finished work to `~/onedrive-outbox/<account>/<relative-path>`.
This does NOT apply to files the user provides directly or files you create from scratch.
```
---
## Step 4: Test the Skill
### Prerequisite
DOC5A must be complete. At least one folder must be synced (add one via the dashboard first).
### Test Matrix
| # | Test | Command / Action | Expected |
|---|---|---|---|
| 1 | Skill loads | Check OpenClaw skill list | `file-sync` appears |
| 2 | Sync status | `exec scripts/sync-status.sh` | Shows both accounts, active/paused, last sync, file counts |
| 3 | List folders | `exec scripts/list-folders.sh firm` | Shows synced folder names |
| 4 | Browse mirror | `exec scripts/list-mirror.sh firm` | Lists files in mirror |
| 5 | Browse subfolder | `exec scripts/list-mirror.sh firm Henderson/` | Lists Henderson folder contents |
| 6 | Request folder | `exec scripts/request-folder.sh firm "TestFolder/" "Testing"` | Request appended to requests.txt |
| 7 | Verify request | `cat ~/sync-config/requests.txt` | Request line present |
| 8 | Dashboard sees request | Check dashboard on will account | Request appears with Approve/Deny |
| 9 | List outbox | `exec scripts/list-outbox.sh firm` | Shows pending/pushed (may be empty) |
| 10 | Create snapshot | `exec scripts/create-snapshot.sh firm Henderson/Henderson_MTD_v3.docx` | Snapshot file created in ~/snapshots/ |
| 11 | List snapshots | `exec scripts/list-snapshots.sh firm` | Shows the snapshot just created |
| 12 | Read log | `exec scripts/read-log.sh firm-mirror` | Shows recent mirror sync log |
### Full Workflow Test
Ask Elnor (or simulate the agent flow):
> "Add a TOA to the Henderson MTD."
Expected sequence:
1. Elnor reads `~/onedrive-mirror/firm/Henderson/Henderson_MTD_v3.docx`
2. Elnor runs `create-snapshot.sh firm Henderson/Henderson_MTD_v3.docx`
3. Elnor copies to `~/working/Henderson_MTD_v3.docx`
4. Elnor edits the working copy (adds TOA)
5. Elnor saves to `~/onedrive-outbox/firm/Henderson/Henderson_MTD_v4.docx`
6. Elnor tells user: "Saved to outbox, will push shortly"
7. Background push (on will account) copies to OneDrive within the sync interval
### Permission Boundary Test
Confirm Elnor CANNOT:
```bash
# These should all fail:
echo "x" >> ~/sync-config/firm-folders.txt # Permission denied
echo "x" >> ~/sync-config/config.json # Permission denied
touch ~/onedrive-mirror/firm/test.txt # Permission denied
cat ~/sync-config/scripts/firm-mirror.sh # Permission denied
curl http://localhost:8417/api/status # 401 (no auth)
```
---
## Files Created by This Build
| File | Location | Purpose |
|---|---|---|
| `SKILL.md` | `~/.openclaw/workspace/skills/file-sync/` | Skill instructions |
| `sync-status.sh` | `~/.openclaw/workspace/skills/file-sync/scripts/` | Overall status |
| `list-folders.sh` | `scripts/` | Show synced folders |
| `list-mirror.sh` | `scripts/` | Browse mirror contents |
| `request-folder.sh` | `scripts/` | Request folder access |
| `list-outbox.sh` | `scripts/` | Show outbox contents |
| `list-snapshots.sh` | `scripts/` | Show snapshots |
| `read-log.sh` | `scripts/` | Read sync logs |
| `create-snapshot.sh` | `scripts/` | Pre-edit backup |
Plus edits to existing Word and Legal Tables SKILL.md files (snapshot instruction block).
---
## What Changed from DOC5
DOC5 §7 specified `skill.json` and `index.js` (a Node.js tool registry pattern). This has been reconciled to the DOC3 R6 architecture:
| DOC5 Original | DOC5B (R6 Reconciled) |
|---|---|
| `skill.json` with tool definitions | SKILL.md YAML frontmatter + body instructions |
| `index.js` with Node.js functions | Shell scripts in `scripts/` called via `exec` |
| Agent calls tools by name from registry | Agent reads SKILL.md, calls `exec scripts/X.sh` |
| Structured JSON returns | Script stdout (text output Elnor parses) |
The functionality is identical. The interface pattern matches how OpenClaw actually discovers and invokes skills.
---
**End of DOC5B.**