Add read_file skill, session persistence, and update script
- New read_file skill: paginated file reading with line ranges, path restricted to /workspace, binary detection, directory listing - Session persistence via SQLite + FTS5: conversation history survives agent restarts, last N messages restored into deque on boot, auto-prune to 1000 messages - Update truncation hint to reference read_file instead of run_command - New scripts/update.sh for patching rootfs + rebuilding snapshot Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17
skills/read_file/SKILL.md
Normal file
17
skills/read_file/SKILL.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: read_file
|
||||
description: Read a file from the workspace with optional line range. Use this to view large tool outputs, logs, or any file in /workspace.
|
||||
parameters:
|
||||
path:
|
||||
type: string
|
||||
description: Path to the file to read (must be under /workspace)
|
||||
required: true
|
||||
offset:
|
||||
type: integer
|
||||
description: Start reading from this line number (default 1)
|
||||
required: false
|
||||
limit:
|
||||
type: integer
|
||||
description: Maximum number of lines to return (default 200)
|
||||
required: false
|
||||
---
|
||||
67
skills/read_file/run.py
Normal file
67
skills/read_file/run.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read a file from /workspace with optional line range."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
args = json.loads(sys.stdin.read())
|
||||
path = args.get("path", "")
|
||||
offset = max(int(args.get("offset", 1)), 1)
|
||||
limit = max(int(args.get("limit", 200)), 1)
|
||||
|
||||
WORKSPACE = os.environ.get("WORKSPACE", "/workspace")
|
||||
|
||||
# Resolve to absolute and ensure it stays under /workspace
|
||||
resolved = os.path.realpath(path)
|
||||
if not resolved.startswith(WORKSPACE + "/") and resolved != WORKSPACE:
|
||||
print(f"[error: path must be under {WORKSPACE}]")
|
||||
sys.exit(0)
|
||||
|
||||
if not os.path.exists(resolved):
|
||||
print(f"[error: file not found: {path}]")
|
||||
sys.exit(0)
|
||||
|
||||
if os.path.isdir(resolved):
|
||||
entries = sorted(os.listdir(resolved))
|
||||
print(f"Directory listing of {path} ({len(entries)} entries):")
|
||||
for entry in entries[:100]:
|
||||
full = os.path.join(resolved, entry)
|
||||
kind = "dir" if os.path.isdir(full) else "file"
|
||||
print(f" {entry} ({kind})")
|
||||
if len(entries) > 100:
|
||||
print(f" ... and {len(entries) - 100} more")
|
||||
sys.exit(0)
|
||||
|
||||
# Binary detection — check first 512 bytes
|
||||
try:
|
||||
with open(resolved, "rb") as f:
|
||||
chunk = f.read(512)
|
||||
if b"\x00" in chunk:
|
||||
size = os.path.getsize(resolved)
|
||||
print(f"[binary file: {path} ({size} bytes)]")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"[error reading file: {e}]")
|
||||
sys.exit(0)
|
||||
|
||||
# Read with line range
|
||||
try:
|
||||
with open(resolved) as f:
|
||||
lines = f.readlines()
|
||||
except Exception as e:
|
||||
print(f"[error reading file: {e}]")
|
||||
sys.exit(0)
|
||||
|
||||
total = len(lines)
|
||||
start_idx = offset - 1 # 0-based
|
||||
end_idx = min(start_idx + limit, total)
|
||||
|
||||
if start_idx >= total:
|
||||
print(f"[file has {total} lines — offset {offset} is beyond end of file]")
|
||||
sys.exit(0)
|
||||
|
||||
for i in range(start_idx, end_idx):
|
||||
print(f"{i + 1}\t{lines[i]}", end="")
|
||||
|
||||
if end_idx < total:
|
||||
print(f"\n[showing lines {offset}-{end_idx} of {total} — use offset={end_idx + 1} to read more]")
|
||||
Reference in New Issue
Block a user