add JSON schema output to all tool scripts on arg parse errors; remove env and grep tools

All tools in ./tools/ now output a JSON schema when arguments fail
to parse, instead of raw usage strings. Formats handled:
- echo/print usage replacement
- dollar-brace-var-colon-? usage expansion to if-statements
- die() function replacement
- argparse ArgumentParser subclass with error() override

Deleted env (use shell instead) and grep (use file_grep instead).
Removed root-level /tools from 9P filesystem listing.
This commit is contained in:
Ollie Agent 2026-07-29 00:01:25 +02:00
parent 45d3caddbc
commit 8630d79116
38 changed files with 116 additions and 101 deletions

2
9p

@ -1 +1 @@
Subproject commit 6b57fe1f7774bca1a1acebe019b615780987acd4
Subproject commit c221426af45cc028ceb9a7ff0fd41aeb297930f4

View File

@ -26,7 +26,7 @@ while [[ $# -gt 0 ]]; do
done
if [ -z "$ID" ]; then
echo "usage: view_denote.sh --id <denote:identifier|identifier>" >&2
echo '{"schema": {"type": "array", "items": [{"name": "--id", "type": "string", "description": "Denote identifier", "required": true}], "examples": [["--id", "20250601T1234"]]}}' >&2
exit 1
fi

View File

@ -1,16 +0,0 @@
#!/usr/bin/env bash
# ollie:parallel read
# ollie:prompt
# ## env
#
# Print the agent's runtime environment variables.
#
# **Args**: (none)
#
# ```
# call_tool: calls=[{tool: "env", args: []}]
# ```
# ollie:end
set -euo pipefail
env | sort

View File

@ -91,7 +91,7 @@ def robust_replace(content, old_string, new_string, replace_all):
return content.replace(match, new_string, 1), None, False
if len(sys.argv) < 4:
print("usage: file_edit <file_path> <old_string> <new_string> [replace_all=true|1|yes]")
print('{"schema": {"type": "array", "items": [{"name": "file_path", "type": "string", "description": "Absolute path to the file to edit", "required": true}, {"name": "old_string", "type": "string", "description": "Text to find (fuzzy whitespace matching)", "required": true}, {"name": "new_string", "type": "string", "description": "Replacement text", "required": true}, {"name": "replace_all", "type": "string", "description": "Set to true/1/yes to replace all occurrences", "required": false}], "examples": [["/abs/path", "old text", "new text"], ["/abs/path", "old", "new", "true"]]}}')
sys.exit(1)
file_path = sys.argv[1]

View File

@ -19,7 +19,7 @@
pattern="$1"
search_path="${2:-$PWD}"
[ -z "$pattern" ] && echo "usage: file_glob <pattern> [path]" && exit 1
[ -z "$pattern" ] && echo '{"schema": {"type": "array", "items": [{"name": "pattern", "type": "string", "description": "Glob pattern (e.g. \"*.go\", \"**/*.ts\")", "required": true}, {"name": "search_dir", "type": "string", "description": "Absolute search directory path (optional, defaults to cwd)", "required": false}], "examples": [["*.go", "/abs/search/dir"], ["**/*.go"]]}}' && exit 1
if [ ! -d "$search_path" ]; then
echo "STATUS=error"

View File

@ -28,7 +28,12 @@ import subprocess
import shutil
import argparse
parser = argparse.ArgumentParser(prog='file_grep', add_help=False)
class _Schema(argparse.ArgumentParser):
def error(self, msg):
print('{"schema": {"type": "array", "items": [{"name": "pattern", "type": "string", "description": "Regex pattern to search", "required": true}, {"name": "--path=DIR", "type": "string", "description": "Search directory", "required": false}, {"name": "--mode=MODE", "type": "string", "description": "content|files_with_matches|count", "required": false}, {"name": "--glob=PATTERN", "type": "string", "description": "File glob filter", "required": false}, {"name": "-i", "type": "string", "description": "Case-insensitive", "required": false}, {"name": "-A N", "type": "string", "description": "Lines after match", "required": false}, {"name": "-B N", "type": "string", "description": "Lines before match", "required": false}, {"name": "-C N", "type": "string", "description": "Lines around match", "required": false}], "examples": [["TODO", "--path=src/", "--glob=*.go"]]}}')
sys.exit(1)
parser = _Schema(prog='file_grep', add_help=False)
parser.add_argument('pattern')
parser.add_argument('--path', default='')
parser.add_argument('--mode', default='content',

View File

@ -43,7 +43,7 @@ for a in sys.argv[1:]:
args.append(a)
if not args:
print("usage: file_read <file_path> [start] [end] [--head=N]")
print('{"schema": {"type": "array", "items": [{"name": "file_path", "type": "string", "description": "Absolute path to the file to read", "required": true}, {"name": "start", "type": "string", "description": "1-indexed start line (optional, default: 1)", "required": false}, {"name": "end", "type": "string", "description": "1-indexed end line, inclusive (optional, max 2000 lines)", "required": false}, {"name": "--head=N", "type": "string", "description": "Read first N lines instead of start/end", "required": false}], "examples": [["/abs/path"], ["/abs/path", "100", "200"], ["/abs/path", "--head=50"]]}}')
sys.exit(1)
file_path = args[0]

View File

@ -28,7 +28,7 @@ def format_diff(path, old, new):
return f"```diff\n{diff}```"
if len(sys.argv) < 3:
print("usage: file_write <file_path> <content>")
print('{"schema": {"type": "array", "items": [{"name": "file_path", "type": "string", "description": "Absolute path to create or overwrite", "required": true}, {"name": "content", "type": "string", "description": "File content to write", "required": true}], "examples": [["/abs/path", "file content here"]]}}')
sys.exit(1)
file_path = sys.argv[1]

View File

@ -1,16 +0,0 @@
#!/usr/bin/env bash
# ollie:parallel read
# ollie:prompt
# ## grep
#
# Run grep directly. Use for piped input or when file_grep is insufficient.
#
# **Args**: `[grep_options..., pattern, file...]`
#
# ```
# call_tool: calls=[{tool: "grep", args: ["-rn", "TODO", "src/"]}]
# ```
# ollie:end
set -uo pipefail
exec grep "$@"

View File

@ -391,7 +391,7 @@ def main():
sys.exit(1)
if len(sys.argv) < 2:
print("usage: gui_accessibility <apps|tree|inspect|find|read|click|type|action> [app] [args...]", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "apps|tree|inspect|find|read|click|type|action", "required": true}, {"name": "app", "type": "string", "description": "App name", "required": false}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["apps"], ["tree", "firefox"]]}}')
sys.exit(1)
cmd = sys.argv[1]
@ -401,7 +401,7 @@ def main():
return
if len(sys.argv) < 3:
print(f"usage: gui_accessibility {cmd} <app_name> [args...]", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "apps|tree|inspect|find|read|click|type|action", "required": true}, {"name": "app", "type": "string", "description": "App name", "required": false}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["apps"], ["tree", "firefox"]]}}')
sys.exit(1)
app_name = sys.argv[2]
@ -421,7 +421,7 @@ def main():
elif cmd == "find":
if len(sys.argv) < 4:
print("usage: gui_accessibility find <app> <role> [name]", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "apps|tree|inspect|find|read|click|type|action", "required": true}, {"name": "app", "type": "string", "description": "App name", "required": false}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["apps"], ["tree", "firefox"]]}}')
sys.exit(1)
target_role = sys.argv[3]
target_name = sys.argv[4] if len(sys.argv) > 4 else ""
@ -435,7 +435,7 @@ def main():
elif cmd == "read":
if len(sys.argv) < 4:
print("usage: gui_accessibility read <app> <path>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "apps|tree|inspect|find|read|click|type|action", "required": true}, {"name": "app", "type": "string", "description": "App name", "required": false}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["apps"], ["tree", "firefox"]]}}')
sys.exit(1)
path = sys.argv[3]
element = resolve_path(app, path)
@ -443,7 +443,7 @@ def main():
elif cmd == "click":
if len(sys.argv) < 4:
print("usage: gui_accessibility click <app> <path>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "apps|tree|inspect|find|read|click|type|action", "required": true}, {"name": "app", "type": "string", "description": "App name", "required": false}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["apps"], ["tree", "firefox"]]}}')
sys.exit(1)
path = sys.argv[3]
element = resolve_path(app, path)
@ -451,7 +451,7 @@ def main():
elif cmd == "type":
if len(sys.argv) < 5:
print("usage: gui_accessibility type <app> <path> <text>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "apps|tree|inspect|find|read|click|type|action", "required": true}, {"name": "app", "type": "string", "description": "App name", "required": false}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["apps"], ["tree", "firefox"]]}}')
sys.exit(1)
path = sys.argv[3]
text = sys.argv[4]
@ -460,7 +460,7 @@ def main():
elif cmd == "action":
if len(sys.argv) < 5:
print("usage: gui_accessibility action <app> <path> <action_name>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "apps|tree|inspect|find|read|click|type|action", "required": true}, {"name": "app", "type": "string", "description": "App name", "required": false}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["apps"], ["tree", "firefox"]]}}')
sys.exit(1)
path = sys.argv[3]
action_name = sys.argv[4]

View File

@ -65,7 +65,8 @@ case "$cmd" in
echo "$id $name"
;;
switch)
target="${2:?usage: gui_activities switch <name_or_id>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|current|switch|create|remove", "required": false}, {"name": "name_or_id", "type": "string", "description": "Activity name or ID (for switch/create/remove)", "required": false}], "examples": [["list"], ["switch", "Development"]]}}'; exit 1; fi
target="${2}"
# Try as ID first
result=$(gdbus call --session --dest "$SERVICE" \
@ -100,7 +101,8 @@ case "$cmd" in
exit 1
;;
create)
name="${2:?usage: gui_activities create <name>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|current|switch|create|remove", "required": false}, {"name": "name_or_id", "type": "string", "description": "Activity name or ID (for switch/create/remove)", "required": false}], "examples": [["list"], ["switch", "Development"]]}}'; exit 1; fi
name="${2}"
id=$(gdbus call --session --dest "$SERVICE" \
--object-path "$OBJ_PATH" \
--method "$IFACE.AddActivity" "$name" 2>/dev/null | \
@ -108,7 +110,8 @@ case "$cmd" in
echo "created: $id ($name)"
;;
remove)
target="${2:?usage: gui_activities remove <name_or_id>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|current|switch|create|remove", "required": false}, {"name": "name_or_id", "type": "string", "description": "Activity name or ID (for switch/create/remove)", "required": false}], "examples": [["list"], ["switch", "Development"]]}}'; exit 1; fi
target="${2}"
# Try as ID directly
gdbus call --session --dest "$SERVICE" \
@ -143,7 +146,7 @@ case "$cmd" in
exit 1
;;
*)
echo "usage: gui_activities <list|current|switch|create|remove> [name_or_id]" >&2
echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|current|switch|create|remove", "required": false}, {"name": "name_or_id", "type": "string", "description": "Activity name or ID (for switch/create/remove)", "required": false}], "examples": [["list"], ["switch", "Development"]]}}' >&2
exit 1
;;
esac

View File

@ -37,7 +37,8 @@ case "$cmd" in
fi
;;
launch)
app="${2:?usage: gui_apps launch <app_name_or_desktop_file>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|launch|activate|installed", "required": true}, {"name": "app", "type": "string", "description": "App name or desktop file (for launch/activate)", "required": false}], "examples": [["list"], ["launch", "firefox"], ["activate", "Firefox"]]}}'; exit 1; fi
app="${2}"
# Try gtk-launch first (handles .desktop files)
if command -v gtk-launch &>/dev/null; then
@ -77,7 +78,8 @@ case "$cmd" in
fi
;;
activate)
app="${2:?usage: gui_apps activate <title_or_class>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|launch|activate|installed", "required": true}, {"name": "app", "type": "string", "description": "App name or desktop file (for launch/activate)", "required": false}], "examples": [["list"], ["launch", "firefox"], ["activate", "Firefox"]]}}'; exit 1; fi
app="${2}"
# Focus existing window
if command -v wmctrl &>/dev/null; then
# Try by class first
@ -110,7 +112,7 @@ case "$cmd" in
done | sort
;;
*)
echo "usage: gui_apps <list|launch|activate|installed> [app]" >&2
echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|launch|activate|installed", "required": true}, {"name": "app", "type": "string", "description": "App name or desktop file (for launch/activate)", "required": false}], "examples": [["list"], ["launch", "firefox"], ["activate", "Firefox"]]}}' >&2
exit 1
;;
esac

View File

@ -44,7 +44,8 @@ case "$cmd" in
echo "${percent}% ($current/$max)"
;;
set)
percent="${2:?usage: gui_brightness set <percent 0-100>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "get|set", "required": true}, {"name": "percent", "type": "string", "description": "Brightness percent 0-100 (for set)", "required": false}], "examples": [["get"], ["set", "75"]]}}'; exit 1; fi
percent="${2}"
if [ "$percent" -lt 0 ] || [ "$percent" -gt 100 ]; then
echo "ERROR: percent must be 0-100" >&2
exit 1
@ -83,7 +84,7 @@ case "$cmd" in
echo "brightness: ${new_pct}% ($value/$max)"
;;
*)
echo "usage: gui_brightness [get|set <percent>|up [step]|down [step]]" >&2
echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "get|set", "required": true}, {"name": "percent", "type": "string", "description": "Brightness percent 0-100 (for set)", "required": false}], "examples": [["get"], ["set", "75"]]}}' >&2
echo "" >&2
echo " get - show current brightness as percentage" >&2
echo " set <0-100> - set brightness to percentage" >&2

View File

@ -29,7 +29,8 @@ case "$cmd" in
sed "s/^('//;s/')$//;s/\\\\n/\n/g"
;;
set)
text="${2:?usage: gui_clipboard set <text>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "get|set|history|clear", "required": true}, {"name": "text", "type": "string", "description": "Text to set (for set action)", "required": false}], "examples": [["get"], ["set", "hello world"], ["history"], ["clear"]]}}'; exit 1; fi
text="${2}"
gdbus call --session --dest "$SERVICE" \
--object-path "$PATH_OBJ" \
--method "$IFACE.setClipboardContents" "$text" >/dev/null
@ -49,7 +50,7 @@ case "$cmd" in
echo "OK: clipboard history cleared"
;;
*)
echo "usage: gui_clipboard [get|set|history|clear] [text|index]" >&2
echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "get|set|history|clear", "required": true}, {"name": "text", "type": "string", "description": "Text to set (for set action)", "required": false}], "examples": [["get"], ["set", "hello world"], ["history"], ["clear"]]}}' >&2
exit 1
;;
esac

View File

@ -14,7 +14,8 @@
# ollie:end
set -euo pipefail
query="${1:?usage: gui_files <query> [--type=TYPE] [--path=DIR] [--limit=N]}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "query", "type": "string", "description": "Search query", "required": true}, {"name": "--type=TYPE", "type": "string", "description": "Filter by file type", "required": false}, {"name": "--path=DIR", "type": "string", "description": "Search directory path", "required": false}, {"name": "--limit=N", "type": "string", "description": "Max results", "required": false}], "examples": [["config", "--type=text"], ["TODO", "--path=/home/user"]]}}'; exit 1; fi
query="${1}"
shift
# Parse flags

View File

@ -14,7 +14,8 @@
# ollie:end
set -euo pipefail
cmd="${1:?usage: gui_input <key|type|click|move|scroll> [args...]}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "subcommand", "type": "string", "description": "key|type|click|move|scroll", "required": true}, {"name": "args", "type": "array", "description": "Subcommand-specific arguments", "required": true}], "examples": [["key", "ctrl+s"], ["type", "hello"], ["click", "left"], ["move", "100", "200"], ["scroll", "down", "3"]]}}'; exit 1; fi
cmd="${1}"
shift
# Detect input method: prefer ydotool (Wayland-safe), fall back to xdotool
@ -30,7 +31,8 @@ fi
case "$cmd" in
key)
# Simulate key press. Examples: "Return", "ctrl+c", "alt+F4", "super"
keys="${1:?usage: gui_input key <key_combo>}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "subcommand", "type": "string", "description": "key|type|click|move|scroll", "required": true}, {"name": "args", "type": "array", "description": "Subcommand-specific arguments", "required": true}], "examples": [["key", "ctrl+s"], ["type", "hello"], ["click", "left"], ["move", "100", "200"], ["scroll", "down", "3"]]}}'; exit 1; fi
keys="${1}"
if [ "$INPUT_TOOL" = "ydotool" ]; then
# ydotool uses key codes; map common names
ydotool key "$keys"
@ -41,7 +43,8 @@ case "$cmd" in
;;
type)
# Type a string
text="${1:?usage: gui_input type <text>}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "subcommand", "type": "string", "description": "key|type|click|move|scroll", "required": true}, {"name": "args", "type": "array", "description": "Subcommand-specific arguments", "required": true}], "examples": [["key", "ctrl+s"], ["type", "hello"], ["click", "left"], ["move", "100", "200"], ["scroll", "down", "3"]]}}'; exit 1; fi
text="${1}"
if [ "$INPUT_TOOL" = "ydotool" ]; then
ydotool type -- "$text"
else
@ -75,8 +78,10 @@ case "$cmd" in
;;
move)
# Move mouse. Args: <x> <y> [--relative]
x="${1:?usage: gui_input move <x> <y> [--relative]}"
y="${2:?usage: gui_input move <x> <y> [--relative]}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "subcommand", "type": "string", "description": "key|type|click|move|scroll", "required": true}, {"name": "args", "type": "array", "description": "Subcommand-specific arguments", "required": true}], "examples": [["key", "ctrl+s"], ["type", "hello"], ["click", "left"], ["move", "100", "200"], ["scroll", "down", "3"]]}}'; exit 1; fi
x="${1}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "subcommand", "type": "string", "description": "key|type|click|move|scroll", "required": true}, {"name": "args", "type": "array", "description": "Subcommand-specific arguments", "required": true}], "examples": [["key", "ctrl+s"], ["type", "hello"], ["click", "left"], ["move", "100", "200"], ["scroll", "down", "3"]]}}'; exit 1; fi
y="${2}"
relative="${3:-}"
if [ "$INPUT_TOOL" = "ydotool" ]; then
if [ "$relative" = "--relative" ]; then
@ -95,7 +100,8 @@ case "$cmd" in
;;
scroll)
# Scroll. Args: <up|down|left|right> [amount]
direction="${1:?usage: gui_input scroll <up|down|left|right> [amount]}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "subcommand", "type": "string", "description": "key|type|click|move|scroll", "required": true}, {"name": "args", "type": "array", "description": "Subcommand-specific arguments", "required": true}], "examples": [["key", "ctrl+s"], ["type", "hello"], ["click", "left"], ["move", "100", "200"], ["scroll", "down", "3"]]}}'; exit 1; fi
direction="${1}"
amount="${2:-3}"
if [ "$INPUT_TOOL" = "ydotool" ]; then
case "$direction" in
@ -115,7 +121,7 @@ case "$cmd" in
echo "scroll: $direction $amount"
;;
*)
echo "usage: gui_input <key|type|click|move|scroll> [args...]" >&2
echo '{"schema": {"type": "array", "items": [{"name": "subcommand", "type": "string", "description": "key|type|click|move|scroll", "required": true}, {"name": "args", "type": "array", "description": "Subcommand-specific arguments", "required": true}], "examples": [["key", "ctrl+s"], ["type", "hello"], ["click", "left"], ["move", "100", "200"], ["scroll", "down", "3"]]}}' >&2
echo "" >&2
echo "Commands:" >&2
echo " key <combo> - press key combo (e.g. ctrl+c, Return, alt+F4)" >&2

View File

@ -256,8 +256,14 @@ def build_playlist(dest, description, root, count, output=None):
print(f"{index:02d}. {track['artist']} — {track['title']}")
class _Schema(argparse.ArgumentParser):
def error(self, msg):
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|status|open|playlist|play|pause|stop|next|previous", "required": true}, {"name": "target", "type": "string", "description": "Target file or player", "required": false}], "examples": [["status"], ["open", "/path/to/song.mp3"]]}}')
sys.exit(1)
def main():
ap = argparse.ArgumentParser()
ap = _Schema()
ap.add_argument("command", choices=["list", "status", "open", "playlist",
"play", "pause", "stop", "next", "previous"])
ap.add_argument("target", nargs="?", help="path, URI, or playlist description")

View File

@ -20,8 +20,14 @@ import argparse
import subprocess
import json
class _Schema(argparse.ArgumentParser):
def error(self, msg):
print('{"schema": {"type": "array", "items": [{"name": "summary", "type": "string", "description": "Notification title", "required": true}, {"name": "body", "type": "string", "description": "Notification body", "required": false}, {"name": "--timeout=MS", "type": "string", "description": "Timeout in ms", "required": false}, {"name": "--actions=LABELS", "type": "string", "description": "Comma-separated actions", "required": false}], "examples": [["Build complete", "All tests passed"]]}}')
sys.exit(1)
def parse_args():
p = argparse.ArgumentParser(description="Desktop notification with optional actions")
p = _Schema(description="Desktop notification with optional actions")
p.add_argument("summary", help="Notification title")
p.add_argument("body", nargs="?", default="", help="Notification body text")
p.add_argument("--actions", default="", help="Comma-separated action labels")

View File

@ -34,8 +34,14 @@ OBJ_PATH = "/org/kde/KWin/ScreenShot2"
IFACE = "org.kde.KWin.ScreenShot2"
class _Schema(argparse.ArgumentParser):
def error(self, msg):
print('{"schema": {"type": "array", "items": [{"name": "mode", "type": "string", "description": "active|screen|area|window", "required": false}, {"name": "--output=PATH", "type": "string", "description": "Output path", "required": false}, {"name": "--title=SUBSTR", "type": "string", "description": "Window title", "required": false}], "examples": [["active"], ["screen", "--output=/tmp/shot.png"]]}}')
sys.exit(1)
def parse_args():
p = argparse.ArgumentParser(description="KDE screenshot via KWin D-Bus")
p = _Schema(description="KDE screenshot via KWin D-Bus")
p.add_argument("mode", nargs="?", default="active",
choices=["active", "screen", "area", "window"],
help="Capture mode (default: active)")

View File

@ -51,8 +51,10 @@ case "$cmd" in
;;
get)
# Get key binding for a shortcut
comp="${2:?usage: gui_shortcuts get <component> <shortcut_name>}"
name="${3:?usage: gui_shortcuts get <component> <shortcut_name>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}'; exit 1; fi
comp="${2}"
if [ $# -lt 3 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}'; exit 1; fi
name="${3}"
gdbus call --session --dest "$SERVICE" \
--object-path "$OBJ_PATH" \
@ -61,8 +63,10 @@ case "$cmd" in
;;
trigger)
# Trigger (invoke) a global shortcut by component/name
comp="${2:?usage: gui_shortcuts trigger <component> <shortcut_name>}"
name="${3:?usage: gui_shortcuts trigger <component> <shortcut_name>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}'; exit 1; fi
comp="${2}"
if [ $# -lt 3 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}'; exit 1; fi
name="${3}"
# First get the shortcut keys, then simulate them
# Or use the invokeShortcut method if available
@ -78,9 +82,12 @@ case "$cmd" in
;;
set)
# Set key binding for a shortcut
comp="${2:?usage: gui_shortcuts set <component> <shortcut_name> <keys>}"
name="${3:?usage: gui_shortcuts set <component> <shortcut_name> <keys>}"
keys="${4:?usage: gui_shortcuts set <component> <shortcut_name> <keys>}"
if [ $# -lt 2 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}'; exit 1; fi
comp="${2}"
if [ $# -lt 3 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}'; exit 1; fi
name="${3}"
if [ $# -lt 4 ]; then echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}'; exit 1; fi
keys="${4}"
gdbus call --session --dest "$SERVICE" \
--object-path "$OBJ_PATH" \
@ -95,7 +102,7 @@ case "$cmd" in
exit 1
;;
*)
echo "usage: gui_shortcuts <list|get|trigger|set> [component] [shortcut] [keys]" >&2
echo '{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|get|trigger|set", "required": true}, {"name": "component", "type": "string", "description": "Component name (for get/trigger/set)", "required": false}, {"name": "shortcut", "type": "string", "description": "Shortcut name (for get/trigger/set)", "required": false}, {"name": "keys", "type": "string", "description": "Key combination (for set)", "required": false}], "examples": [["list"], ["get", "kwin", "switch window"], ["trigger", "kwin", "switch window"]]}}' >&2
echo "" >&2
echo "Examples:" >&2
echo " gui_shortcuts list - list all components" >&2

View File

@ -373,7 +373,7 @@ def switch_desktop(num):
def main():
if len(sys.argv) < 2:
print("usage: gui_windows <list|focus|close|minimize|maximize|move|resize|desktops|desktop> [args...]", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
cmd = sys.argv[1]
@ -382,39 +382,39 @@ def main():
list_windows()
elif cmd == "focus":
if len(sys.argv) < 3:
print("usage: gui_windows focus <title_substr>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
focus_window(sys.argv[2])
elif cmd == "close":
if len(sys.argv) < 3:
print("usage: gui_windows close <title_substr>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
close_window(sys.argv[2])
elif cmd == "minimize":
if len(sys.argv) < 3:
print("usage: gui_windows minimize <title_substr>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
minimize_window(sys.argv[2])
elif cmd == "maximize":
if len(sys.argv) < 3:
print("usage: gui_windows maximize <title_substr>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
maximize_window(sys.argv[2])
elif cmd == "move":
if len(sys.argv) < 5:
print("usage: gui_windows move <title_substr> <x> <y>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
move_window(sys.argv[2], sys.argv[3], sys.argv[4])
elif cmd == "resize":
if len(sys.argv) < 5:
print("usage: gui_windows resize <title_substr> <w> <h>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
resize_window(sys.argv[2], sys.argv[3], sys.argv[4])
elif cmd == "desktops":
list_desktops()
elif cmd == "desktop":
if len(sys.argv) < 3:
print("usage: gui_windows desktop <number>", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "action", "type": "string", "description": "list|focus|close|minimize|maximize|move|resize|desktops|desktop", "required": true}, {"name": "args...", "type": "string", "description": "Action-specific arguments", "required": false}], "examples": [["list"], ["focus", "Firefox"]]}}')
sys.exit(1)
switch_desktop(sys.argv[2])
else:

View File

@ -25,7 +25,7 @@ for a in sys.argv[1:]:
file_path = a
if not file_path:
print("usage: image_read <file_path>")
print('{"schema": {"type": "array", "items": [{"name": "file_path", "type": "string", "description": "Absolute path to image file", "required": true}, {"name": "--region=x,y,w,h", "type": "string", "description": "Region to capture (optional)", "required": false}, {"name": "--scale=N", "type": "string", "description": "Scale factor (optional)", "required": false}], "examples": [["/abs/path/image.png"], ["/abs/path/image.png", "--region=0,0,100,100"]]}}')
sys.exit(1)
if not os.path.isabs(file_path):

View File

@ -21,7 +21,7 @@ sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.ab
from _lib.lsp.client import request
if len(sys.argv) < 4:
print("usage: lsp_completion <file> <line> <col>")
print('{"schema": {"type": "array", "items": [{"name": "file", "type": "string", "description": "File path", "required": true}, {"name": "line", "type": "string", "description": "Line number (1-indexed)", "required": true}, {"name": "col", "type": "string", "description": "Column number", "required": true}], "examples": [["/path/to/file.go", "42", "5"]]}}')
sys.exit(1)
filepath = os.path.abspath(sys.argv[1])

View File

@ -21,7 +21,7 @@ sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.ab
from _lib.lsp.client import request
if len(sys.argv) < 4:
print("usage: lsp_definition <file> <line> <col>")
print('{"schema": {"type": "array", "items": [{"name": "file", "type": "string", "description": "File path", "required": true}, {"name": "line", "type": "string", "description": "Line number (1-indexed)", "required": true}, {"name": "col", "type": "string", "description": "Column number", "required": true}], "examples": [["/path/to/file.go", "42", "5"]]}}')
sys.exit(1)
filepath = os.path.abspath(sys.argv[1])

View File

@ -19,7 +19,7 @@ sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.ab
from _lib.lsp.client import request
if len(sys.argv) < 2:
print("usage: lsp_diagnostics <file>")
print('{"schema": {"type": "array", "items": [{"name": "file", "type": "string", "description": "File path", "required": true}], "examples": [["/path/to/file.go"]]}}')
sys.exit(1)
filepath = os.path.abspath(sys.argv[1])

View File

@ -19,7 +19,7 @@ sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.ab
from _lib.lsp.client import request
if len(sys.argv) < 4:
print("usage: lsp_hover <file> <line> <col>")
print('{"schema": {"type": "array", "items": [{"name": "file", "type": "string", "description": "File path", "required": true}, {"name": "line", "type": "string", "description": "Line number (1-indexed)", "required": true}, {"name": "col", "type": "string", "description": "Column number", "required": true}], "examples": [["/path/to/file.go", "42", "5"]]}}')
sys.exit(1)
filepath = os.path.abspath(sys.argv[1])

View File

@ -23,7 +23,7 @@ sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.ab
from _lib.lsp.client import request
if len(sys.argv) < 4:
print("usage: lsp_references <file> <line> <col>")
print('{"schema": {"type": "array", "items": [{"name": "file", "type": "string", "description": "File path", "required": true}, {"name": "line", "type": "string", "description": "Line number (1-indexed)", "required": true}, {"name": "col", "type": "string", "description": "Column number", "required": true}], "examples": [["/path/to/file.go", "42", "5"]]}}')
sys.exit(1)
filepath = os.path.abspath(sys.argv[1])

View File

@ -20,7 +20,7 @@ sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.ab
from _lib.lsp.client import request
if len(sys.argv) < 5:
print("usage: lsp_rename <file> <line> <col> <new_name>")
print('{"schema": {"type": "array", "items": [{"name": "file", "type": "string", "description": "File path", "required": true}, {"name": "line", "type": "string", "description": "Line number (1-indexed)", "required": true}, {"name": "col", "type": "string", "description": "Column number", "required": true}, {"name": "new_name", "type": "string", "description": "New symbol name", "required": true}], "examples": [["/path/to/file.go", "42", "5", "newName"]]}}')
sys.exit(1)
filepath = os.path.abspath(sys.argv[1])

View File

@ -20,7 +20,7 @@ sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.ab
from _lib.lsp.client import request
if len(sys.argv) < 2:
print("usage: lsp_symbols <file_or_query> [--workspace]")
print('{"schema": {"type": "array", "items": [{"name": "file_or_query", "type": "string", "description": "File path or search query", "required": true}, {"name": "--workspace", "type": "string", "description": "Search entire workspace", "required": false}], "examples": [["/path/to/file.go"], ["function_name", "--workspace"]]}}')
sys.exit(1)
workspace = "--workspace" in sys.argv

View File

@ -21,7 +21,7 @@
# - If recall returns nothing relevant, proceed without it.
# ollie:end
[ -z "$*" ] && echo "usage: memory_recall <query>" && exit 1
[ -z "$*" ] && echo '{"schema": {"type": "array", "items": [{"name": "query", "type": "string", "description": "Search keyword or phrase", "required": true}], "examples": [["architecture"], ["config"]]}}' && exit 1
mem_dir="${OLLIE_MEMORY_PATH:-$HOME/.config/ollie/memory}"

View File

@ -30,7 +30,7 @@ import re
from datetime import datetime
if len(sys.argv) < 4:
print("usage: memory_remember <title> <tags_csv> <body>")
print('{"schema": {"type": "array", "items": [{"name": "title", "type": "string", "description": "Short noun phrase", "required": true}, {"name": "tags_csv", "type": "string", "description": "Comma-separated tags", "required": true}, {"name": "body", "type": "string", "description": "The fact to remember", "required": true}], "examples": [["API endpoint", "api,backend", "The /users endpoint returns..."]]}}')
sys.exit(1)
title = sys.argv[1]

View File

@ -13,7 +13,8 @@
set -euo pipefail
pid="${1:?usage: process_dismiss <pid>}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "pid", "type": "string", "description": "Process PID", "required": true}], "examples": [["12345"]]}}'; exit 1; fi
pid="${1}"
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
dbus-send --session --dest=org.ollie.SessionManager --type=method_call --print-reply \

View File

@ -14,7 +14,8 @@
set -euo pipefail
pid="${1:?usage: process_output <pid>}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "pid", "type": "string", "description": "Process PID", "required": true}], "examples": [["12345"]]}}'; exit 1; fi
pid="${1}"
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
# Use gdbus for proper parsing of string with newlines

View File

@ -14,7 +14,8 @@
set -euo pipefail
pid="${1:?usage: process_signal <pid> [TERM|KILL]}"
if [ $# -lt 1 ]; then echo '{"schema": {"type": "array", "items": [{"name": "pid", "type": "string", "description": "Process PID", "required": true}, {"name": "signal", "type": "string", "description": "Signal name (TERM or KILL, default: TERM)", "required": false}], "examples": [["12345", "TERM"], ["12345"]]}}'; exit 1; fi
pid="${1}"
signal="${2:-TERM}"
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"

View File

@ -20,5 +20,5 @@
# - Keep under 200 words. State the decision and key tradeoffs, not stream-of-consciousness.
# ollie:end
[ -z "$*" ] && echo "usage: reasoning_think <thought>" && exit 1
[ -z "$*" ] && echo '{"schema": {"type": "array", "items": [{"name": "thought", "type": "string", "description": "Your reasoning", "required": true}], "examples": [["Need to check if config file exists first"]]}}' && exit 1
exit 0

View File

@ -18,7 +18,7 @@ SERVICE="org.ollie.SessionManager"
OBJ="/org/ollie/SessionManager"
IFACE="org.ollie.SessionManager"
die() { printf 'route: %s\n' "$*" >&2; exit 1; }
die() { echo '{"schema": {"type": "array", "items": [{"name": "instruction", "type": "string", "description": "Task description to route", "required": true}, {"name": "--tier=worker|expert", "type": "string", "description": "Model tier (optional)", "required": false}, {"name": "--backend=<name>", "type": "string", "description": "Backend override (optional)", "required": false}], "examples": [["implement login page"], ["refactor auth", "--tier=expert"]]}}'; exit 1; }
dbus_call() {
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \

View File

@ -33,7 +33,7 @@ TEMPLATE = {
def die(msg):
print(f"subagent_generate: {msg}", file=sys.stderr)
print('{"schema": {"type": "array", "items": [{"name": "json_config", "type": "string", "description": "Optional JSON config. Empty = prints template", "required": false}], "examples": [[], ["{\"role\": \"backend dev\"}"]]}}')
sys.exit(1)

View File

@ -19,7 +19,7 @@ SERVICE="org.ollie.SessionManager"
OBJ="/org/ollie/SessionManager"
IFACE="org.ollie.SessionManager"
die() { printf 'subagent_spawn: %s\n' "$*" >&2; exit 1; }
die() { echo '{"schema": {"type": "array", "items": [{"name": "prompt", "type": "string", "description": "Task prompt for the subagent", "required": true}, {"name": "-name", "type": "string", "description": "Subagent name (optional)", "required": false}, {"name": "-agent", "type": "string", "description": "Agent config (optional)", "required": false}, {"name": "-backend", "type": "string", "description": "Backend (optional)", "required": false}, {"name": "-model", "type": "string", "description": "Model (optional)", "required": false}], "examples": [["-name", "worker", "implement login page"], ["fix tests"]]}}'; exit 1; }
dbus_call() {
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \