With artificial intelligence, we are summoning the demon, Elon Musk
AI won’t replace humans, but those who use AI will replace those who don’t, Garry Kasparov
AI is not a threat; it’s a tool, but what a f**ng tool, Anawim, justothepoint.com

In Parts 1 through 3 we gave our OpenClaw agent a body (the Gateway and workspace), a memory (skills, QMD), and senses (Gmail, Calendar, search). But an autonomous agent that can only talk is an advisor. In this fourth installment, we give it hands.
OpenClaw's file tools give the agent direct access to the machine it runs on — no copy-paste required. But 'file management' is really six tools with different risk profiles, and the difference between a useful agent and a dangerous one is knowing which tool to reach for and which permission mode to leave it in.
We begin with a detour through Obsidian: its new official CLI turns your notes vault into a structured, scriptable knowledge base the agent can search, read, and write without ever leaving the terminal. Then we take an honest tour of the file tool surface — read, write, edit, search, and the powerful but dangerous exec — work through a realistic example (installing a zsh function), and finish with the permission model and safety checklist that let you sleep while your agent works.
Obsidian now ships an official CLI that controls the running desktop app from your terminal. This is preferable to third‑party wrappers because it guarantees compatibility, avoids extra dependencies, and lets you script the vault directly.
obsidian version, obsidian help.~/Library/Application Support/obsidian/obsidian.json. This is the source of truth for which vaults exist and which one is open. The OpenClaw Obsidian skill reads this file rather than guessing paths.# A. Command grammar
# The CLI uses key=value parameters and boolean flags:
obsidian command [name=value ...] [flag]
# format=json gives structured output, which is what you want when an agent is parsing results.
obsidian vault="Notes" search query="meeting notes" format=json
# B. Open and read
# file=name uses Obsidian-style name resolution.
obsidian open file=Agenda.md
# path="Folder/Note.md" is exact vault-relative resolution.
obsidian open path="assets/Todo.md"
# obsidian read with no argument reads the active file
obsidian read
# C. Search
# matches returns matching lines; format=json returns structured results
obsidian search query="TODO" matches
obsidian search query="TODO" matches format=json
# D. Create, append
obsidian create name="New Note"
obsidian create path="Inbox/Idea.md" content="# Idea"
obsidian append file=Note content="New line"
# E. Move, delete
obsidian move file=Note to=Archive/
obsidian move path="Inbox/Old.md" to="Projects/New.md"
obsidian delete file=Note
OpenClaw’s file tools give the agent direct access to the machine it runs on — no copy‑paste required. But “file management” is really six tools with different risk profiles, and the difference between a useful agent and a dangerous one is knowing which tool to reach for and which permission mode to leave it in.
This article covers the tool surface, a realistic worked example (adding a zsh function), the exec parameters that matter, and the correct way to put exec behind approval.
OpenClaw’s file tools give the agent direct access to the machine it runs on — no copy‑paste required. But “file management” is really six tools with different risk profiles, and the difference between a useful agent and a dangerous one is knowing which tool to reach for and which permission mode to leave it in.
This article covers the tool surface, a realistic worked example (adding a zsh function), the exec parameters that matter, and the correct way to put exec behind approval.
| Tool | Group | What it is for | Risk |
|---|---|---|---|
read |
group:fs |
Read file contents (text or images), with offset/limit for large files | Low (read-only) |
write |
group:fs |
Create or overwrite a file; creates parent directories | High (overwrites silently) |
edit |
group:fs |
Exact string replacement in an existing file | Medium–high (a wrong match corrupts) |
apply_patch |
group:fs |
Structured diff-based patching (experimental) | Medium |
exec |
group:runtime |
Run shell commands, foreground or background | Highest |
process |
group:runtime |
Poll, log, kill, clear background exec sessions |
Medium |
The single most important habit: read first, then edit or write. write overwrites without warning, and edit fails (or corrupts) if the exact string is not present.
# Prompt Example
Read README.md in my Python directory and summarize it.
# Output
Here's a summary of the README.md in ~/macOS/myPython:
Hugo Content Toolkit (v0.17)
A Python toolkit that reviews, enriches, and quality-checks Markdown pages of your Hugo website (justtothepoint) using a local LLM (Ollama) and the live Hugo server. [...]
edit does an exact string replacement, not a regex and not a fuzzy match. If whitespace differs — tabs vs spaces, trailing spaces — the edit fails. That is a safety feature: it will not silently match the wrong occurrence. The practical fix is to copy the exact block from a fresh read, then replace it.
If you do not want the agent touching dotfiles or system paths, set:
{
# Safety measure: Keep the agent inside the workspace
"tools": {
"fs": {
"workspaceOnly": true
}
}
}
This restricts read/write/edit to the workspace directory. It does not restrict exec — which is exactly why the two controls must be considered separately.
Most file tasks start with “find where this is.” exec plus ripgrep is the right tool; read is the wrong one.
# Find files containing a phrase
rg -l 'Talonario Recetas' ~/macOS
# Case-insensitive, with line numbers
rg -in 'talonario recetas' ~/macOS
# Restrict to Markdown, skip .git and node_modules
rg -l -g '*.md' -g '!.git/**' -g '!node_modules/**' 'Talonario Recetas' ~/macOS
Practical notes:
-l lists filenames only; drop it to see matching lines.--hidden includes dotfiles; --no-ignore ignores .gitignore. Both widen the search and can produce noise, so add -g '!.git/**' when you use them.-F) when the pattern contains regex metacharacters such as . or [.hugo-image-posts to .zsh_functionsThe goal: a function that, given an image filename, prints every Hugo post that references it.
hugo-image-posts() {
local content="/Volumes/MacDatosMaster/justtothepoint/content"
# A **zsh history modifier** that takes the tail (basename) of a path.
# `hugo-image-posts /a/b/hero.png` searches for `hero.png`, not the full path.
local image="${1:t}" # Use the filename even if you pass a full path
if [[ -z "$1" ]]; then
# Writes to **stderr** (fd 2), so the message is not captured as normal output.
print -u2 -- "Usage: hugo-image-posts "
# Distinct exit codes: `2` = usage error, `1` = missing directory.
return 2
fi
if [[ ! -d "$content" ]]; then
print -u2 -- "Hugo content directory not found: $content"
return 1
fi
# `rg -F`: **Fixed-string** match. The `.` in `hero.png` is literal, not “any character.”
# `rg -l`: Print **filenames only**, which is what you want for “which posts reference this.”
# `--sort path`: Deterministic output order
# `--hidden --no-ignore`: Includes hidden files and ignores `.gitignore`.
# `-g '*.md'`: Limits to Markdown.
# `-- "$image"`: The `--` separates options from the pattern, so a filename starting with `-` is not parsed as a flag.
rg -F -l --sort path --hidden --no-ignore -g '*.md' -- "$image" "$content"
}
Add the following zsh function to `~/.zsh_functions`.
First read the file and inspect its contents. Preserve everything already there. Append the function at the end only if it is not already present; if it is present, update that function without changing unrelated content. Use a targeted patch rather than rewriting the whole file.
After editing, run `zsh -n ~/.zsh_functions` to check the file’s syntax. Provide a comment to explain its functionality.
Function to add:
hugo-image-posts() {
local content="/Volumes/MacDatosMaster/justtothepoint/content"
local image="${1:t}"
if [[ -z "$1" ]]; then
print -u2 -- "Usage: hugo-image-posts "
return 2
fi
if [[ ! -d "$content" ]]; then
print -u2 -- "Hugo content directory not found: $content"
return 1
fi
rg -F -l --sort path --hidden --no-ignore -g '*.md' -- "$image" "$content"
}
Then, reload and test:
source ~/.zsh_functions
hugo-image-posts MomSittingThinkingBedroomDog.jpg
Expected output is the list of .md files, exactly as in the example.
exec tool: parameters that matterexec is not just “run a command.” These parameters change its behaviour:
| Parameter | Default | Practical meaning |
|---|---|---|
command |
required | The shell command. |
workdir |
current directory | Set it explicitly when the command depends on a project root. |
timeout |
1800 s | Kills the process on expiry. Long enough for builds; too long for a hung command you forgot about. |
yieldMs |
10000 ms | After 10 s, the command is moved to the background automatically. |
background |
false |
Background immediately. |
pty |
false |
Use a pseudo-terminal — needed for TTY-only CLIs, interactive coding agents, and terminal UIs. |
host |
sandbox |
Where it runs: sandbox, gateway, or node. |
security |
deny (sandbox) / allowlist (gateway + node) |
deny, allowlist, or full. |
ask |
on-miss |
off, on-miss, or always — approval prompts for gateway/node. |
elevated |
false |
Requests elevated mode on the gateway host. |
With host=gateway, OpenClaw merges your login-shell PATH into the exec environment, but the daemon itself runs with a minimal PATH — on macOS, /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin. If a command works in your terminal but not through the agent, the usual cause is that the binary lives somewhere the daemon does not see. Therefore, you must fix the environment (tools.exec.pathPrepend), not the command.
# Run commands that match the configured allowlist, and request human approval for commands that don’t.
{
"tools": {
"exec": {
"mode": "ask"
}
}
}
Or from the CLI:
openclaw config set tools.exec.mode ask
openclaw approvals get # verify the effective policy
openclaw gateway restart # apply
tools.exec.mode is the normalized policy interface. Each mode resolves to an underlying security / ask pair:
| Mode | security / ask |
Behaviour | Use when |
|---|---|---|---|
deny |
deny / off |
Blocks host exec entirely | No host commands should run |
allowlist |
allowlist / off |
Runs allowlisted commands; silently refuses misses | You have a known-safe command set |
ask |
allowlist / on-miss |
Runs allowlisted commands; prompts a human on a miss | Every new command form should be reviewed |
auto |
allowlist / on-miss |
Same, plus a native automatic reviewer before human approval | Coding sessions that need practical but guarded access |
full |
full / off |
Runs host exec without prompts | A trusted host/session only |
The docs’ recommended default for coding agents is auto, not ask: it keeps the allowlist and only escalates to a human when the automatic reviewer cannot safely approve.
rg -l before sed -i, ls before mv, --dry-run where supported.rm. On macOS, brew install trash gives a trash command; or move files into a dated folder instead of deleting them.write or a bad patch.tools.fs.workspaceOnly: true if the agent has no business reading dotfiles or system paths.exec separately. exec is mutating even when write/edit are disabled.openclaw approvals get, not by reading the config file alone.# Read / search
rg -l -g '*.md' -g '!.git/**' 'pattern' ~/macOS
rg -in 'pattern' path/
# Safe edit workflow
# 1) read the target 2) locate a unique anchor 3) edit/apply_patch 4) re-read to confirm
# zsh inventory
zsh -ic 'alias -L'
zsh -ic 'functions'
# exec policy
openclaw config set tools.exec.mode ask
openclaw approvals get
openclaw gateway restart
# Inspect what the agent can actually do
openclaw config get tools.fs
openclaw config get tools.exec
openclaw skills install @nesdeq/openclaw-feeds# Create a virtual environment for that skill.
cd ~/macOS/claude/skills/openclaw-feeds
python3 -m venv .venv
# Install feedparser there.
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install feedparser
# Run the script with that environment’s Python:
.venv/bin/python scripts/feeds.py --category news
### 5. Tech News — latest headlines
Use the `openclaw-feeds` skill to fetch the latest tech news. Run from the skill directory with the venv Python:
```bash
~/macOS/claude/skills/openclaw-feeds/venv/bin/python3 ~/macOS/claude/skills/openclaw-feeds/scripts/feeds.py --category news
```
- Parse the JSON output. The first element is metadata; the rest are entries with `title`, `url`, `source`, `date`, `summary`.
- Select the 5 most interesting or highest-priority headlines.
- Group by theme if there are clear clusters (e.g. AI, Hardware, Industry, Security).
- For each: **headline as markdown link** — one-line summary *(source)*.
- If zero entries or error, say "tech news unavailable" — do not invent headlines.
[...]
## Output format
📰 Tech News
- **<headline>**(url) — summary *(source)*
- ...
SKILL="$HOME/macOS/claude/skills/daily-gospel"
mkdir -p "$SKILL/scripts"
python3 -m venv "$SKILL/venv"
"$SKILL/venv/bin/python3" -m pip install feedparser
nvim ~/macOS/claude/skills/daily-gospel/scripts/daily_gospel.py.
import json
from html.parser import HTMLParser
import feedparser
FEED_URL = "https://rss.evangelizo.org/rss/v2/evangelizo_rss-am.xml"
class TextOnly(HTMLParser):
def __init__(self):
super().__init__()
self.parts = []
def handle_data(self, data):
self.parts.append(data)
feed = feedparser.parse(FEED_URL)
if not feed.entries:
raise SystemExit(f"No entries found in feed. Parser error: {feed.get('bozo_exception', 'unknown')}")
entry = feed.entries[0]
content = entry.get("content", [])
raw_text = content[0].get("value", "") if content else entry.get("summary", "")
parser = TextOnly()
parser.feed(raw_text)
reading = " ".join(" ".join(parser.parts).split())
result = {
"title": entry.get("title", ""),
"date": entry.get("published", entry.get("updated", "")),
"reading": reading,
"link": entry.get("link", ""),
}
print(json.dumps(result, ensure_ascii=False, indent=2))
"$HOME/macOS/claude/skills/daily-gospel/venv/bin/python3" \
"$HOME/macOS/claude/skills/daily-gospel/scripts/daily_gospel.py"
nvim ~/macOS/claude/skills/daily-gospel/SKILL.md
---
name: daily-gospel
description: Fetch the latest daily Gospel reading from the Evangelizo RSS feed.
---
When the user asks for the daily Gospel or daily reading, run:
`{baseDir}/venv/bin/python3 {baseDir}/scripts/daily_gospel.py`
Present the title, date, and reading. Include the feed link if useful. If the script reports an error or no entry, report that instead of inventing a reading.
I want a skill like ClawHub Finance Skill. It tracks stocks, ETFs, indices, crypto (where available), and FX pairs with caching + provider fallbacks.
Install the intended ClawHub finance skill. First identify the exact skill name, publisher, and version, and inspect its SKILL.md, scripts, and requirements.txt. Explain what it will install and run, and wait for my approval before making changes. After approval, install the skill, create a .venv inside the skill directory with uv, and install only the listed requirements into that environment. Do not install packages into system Python, use sudo, or add API keys. Run scripts with that venv’s Python. At the end, tell me the installed path, dependencies, and any test results.
Answer:
📋 ClawHub Finance Skill — Inspection Report
Skill: @anton-roos/finance v1.1.2
Publisher: anton-roos (community, not official)
Security audit: ✅ Safe — no hidden or destructive behavior found
Downloads: 14,255 | Stars: varies
[...]
What I'll do (pending your approval)
Create .venv inside ~/macOS/claude/skills/finance/ using uv
Install only yfinance, pandas, requests into that venv (no system Python, no sudo)
Run a test quote to verify it works
Report installed path, dependencies, and test results
No system changes. No API keys. No sudo.
Approve to proceed?
After approval,
[...]
✅ Finance Skill Installed & Tested
Item Detail
Skill @anton-roos/finance v1.1.2
Path ~/macOS/claude/skills/finance/
Venv .venv/ (Python 3.15, created with uv)
Security ✅ Safe (ClawHub audit)
[...]
How to use
cd ~/macOS/claude/skills/finance
# Quote
.venv/bin/python scripts/market_quote.py AAPL
.venv/bin/python scripts/market_quote.py EURUSD
Using the skill:
/finance compare AAPL,MSFT,GOOGL