vet

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit a21f6bccf3222c4482649e08df251f6ecd637d1f
parent 95ea24e4c91a9073da80a4739828cf06819a752d
Author: andrewlaack-collab <andrew.laack@imbue.com>
Date:   Thu,  5 Feb 2026 21:41:12 +0000

Updated readme (#17)

* Updated readme

* Refactoring + support proprietary garbage (claude code)
Diffstat:
MREADME.md | 21++++++++++++++-------
Mskills/vet/SKILL.md | 15++++++++++-----
Askills/vet/scripts/export_claude_code_session.py | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mskills/vet/scripts/export_codex_session.py | 15++++++---------
Mskills/vet/scripts/export_opencode_session.py | 10+++++-----
5 files changed, 96 insertions(+), 26 deletions(-)

diff --git a/README.md b/README.md @@ -31,24 +31,31 @@ Vet ships as an [agent skill](https://agentskills.io) that coding agents like [O ### Install the skill globally ```bash -mkdir -p ~/.agents/skills/vet/scripts && \ +for dir in ~/.agents ~/.claude ~/.codex; do + mkdir -p "$dir/skills/vet/scripts" curl -fsSL https://raw.githubusercontent.com/imbue-ai/vet/main/skills/vet/SKILL.md \ - -o ~/.agents/skills/vet/SKILL.md && \ + -o "$dir/skills/vet/SKILL.md" curl -fsSL https://raw.githubusercontent.com/imbue-ai/vet/main/skills/vet/scripts/export_opencode_session.py \ - -o ~/.agents/skills/vet/scripts/export_opencode_session.py && \ + -o "$dir/skills/vet/scripts/export_opencode_session.py" curl -fsSL https://raw.githubusercontent.com/imbue-ai/vet/main/skills/vet/scripts/export_codex_session.py \ - -o ~/.agents/skills/vet/scripts/export_codex_session.py + -o "$dir/skills/vet/scripts/export_codex_session.py" + curl -fsSL https://raw.githubusercontent.com/imbue-ai/vet/main/skills/vet/scripts/export_claude_code_session.py \ + -o "$dir/skills/vet/scripts/export_claude_code_session.py" +done ``` -This places the skill in `~/.agents/skills/vet/`, which is discovered by both OpenCode and Codex. +This places the skill in `~/.agents/skills/vet/`, `~/.claude/skills/vet/`, and `~/.codex/skills/vet/`, so it is discovered by OpenCode, Claude Code, and Codex. ### Install per-project To have agents use vet automatically in a specific repo, copy the skill into the project: ```bash -cp -r /path/to/vet/skills/vet .agents/skills/vet -git add .agents/skills/vet && git commit -m "Add vet agent skill" +for dir in .agents .claude .codex; do + cp -r /path/to/vet/skills/vet "$dir/skills/vet" +done +git add .agents/skills/vet .claude/skills/vet .codex/skills/vet && \ + git commit -m "Add vet agent skill" ``` ## How it works diff --git a/skills/vet/SKILL.md b/skills/vet/SKILL.md @@ -29,18 +29,21 @@ vet --help ### Standard Usage -Always include conversation history for best results. Set the session environment variable and use `--history-loader`: +Always include conversation history for best results. Pass the session identifier to the export script via `--history-loader`: **OpenCode:** ```bash -export VET_SESSION_ID="<current-session-id>" -vet "goal" --history-loader "python ~/.agents/skills/vet/scripts/export_opencode_session.py" +vet "goal" --history-loader "python ~/.agents/skills/vet/scripts/export_opencode_session.py --session-id <session-uuid>" ``` **Codex:** ```bash -export CODEX_SESSION_FILE="<path-to-current-session.jsonl>" -vet "goal" --history-loader "python ~/.agents/skills/vet/scripts/export_codex_session.py" +vet "goal" --history-loader "python ~/.codex/skills/vet/scripts/export_codex_session.py --session-file <path-to-session.jsonl>" +``` + +**Claude Code:** +```bash +vet "goal" --history-loader "python ~/.claude/skills/vet/scripts/export_claude_code_session.py --session-file <path-to-session.jsonl>" ``` ### Finding Your Session @@ -49,6 +52,8 @@ vet "goal" --history-loader "python ~/.agents/skills/vet/scripts/export_codex_se **Codex:** Session files are stored in `~/.codex/sessions/YYYY/MM/DD/`. Find the most recently modified `.jsonl` file. +**Claude Code:** Session files are stored in `~/.claude/projects/<encoded-path>/`. The encoded path replaces `/` with `-` (e.g. `/home/user/myproject` becomes `-home-user-myproject`). Find the most recently modified `.jsonl` file in the directory matching your project. + ## Common Options - `--base-commit REF`: Git ref for diff base (default: HEAD) diff --git a/skills/vet/scripts/export_claude_code_session.py b/skills/vet/scripts/export_claude_code_session.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +import argparse +import json +import sys +from pathlib import Path + +parser = argparse.ArgumentParser(description="Export Claude Code session history for vet") +parser.add_argument("--session-file", required=True, help="Path to Claude Code session .jsonl file") +args = parser.parse_args() + +SESSION_FILE = Path(args.session_file) +if not SESSION_FILE.exists(): + sys.exit(0) + +for line in SESSION_FILE.read_text().splitlines(): + if not line.strip(): + continue + try: + entry = json.loads(line) + except json.JSONDecodeError as e: + print( + f"WARNING: Skipping malformed JSON line in {SESSION_FILE}: {e}", + file=sys.stderr, + ) + continue + + entry_type = entry.get("type") + if entry_type not in ("user", "assistant"): + continue + + if entry.get("isSidechain"): + continue + + message = entry.get("message", {}) + content = message.get("content") + + if entry_type == "user": + if isinstance(content, str) and content.strip(): + print(json.dumps({"object_type": "ChatInputUserMessage", "text": content})) + elif isinstance(content, list): + text = " ".join(c.get("text", "") for c in content if isinstance(c, dict) and c.get("type") == "text") + if text.strip(): + print(json.dumps({"object_type": "ChatInputUserMessage", "text": text})) + elif entry_type == "assistant": + if not isinstance(content, list): + continue + blocks = [] + for c in content: + if isinstance(c, dict) and c.get("type") == "text" and c.get("text"): + blocks.append({"object_type": "TextBlock", "type": "text", "text": c["text"]}) + if blocks: + print( + json.dumps( + { + "object_type": "ResponseBlockAgentMessage", + "role": "assistant", + "assistant_message_id": message.get("id", entry.get("uuid", "claude_code_msg")), + "content": blocks, + } + ) + ) diff --git a/skills/vet/scripts/export_codex_session.py b/skills/vet/scripts/export_codex_session.py @@ -1,18 +1,15 @@ #!/usr/bin/env python3 +import argparse import json -import os import sys from pathlib import Path -SESSION_FILE = os.environ.get("CODEX_SESSION_FILE") -if not SESSION_FILE: - sessions_dir = Path.home() / ".codex/sessions" - if sessions_dir.exists(): - files = list(sessions_dir.rglob("*.jsonl")) - if files: - SESSION_FILE = str(max(files, key=lambda f: f.stat().st_mtime)) +parser = argparse.ArgumentParser(description="Export Codex session history for vet") +parser.add_argument("--session-file", required=True, help="Path to Codex session .jsonl file") +args = parser.parse_args() -if not SESSION_FILE or not Path(SESSION_FILE).exists(): +SESSION_FILE = args.session_file +if not Path(SESSION_FILE).exists(): sys.exit(0) for line in Path(SESSION_FILE).read_text().splitlines(): diff --git a/skills/vet/scripts/export_opencode_session.py b/skills/vet/scripts/export_opencode_session.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 +import argparse import json -import os import sys from pathlib import Path -SESSION_ID = os.environ.get("VET_SESSION_ID") -if not SESSION_ID: - sys.exit(0) +parser = argparse.ArgumentParser(description="Export OpenCode session history for vet") +parser.add_argument("--session-id", required=True, help="OpenCode session UUID") +args = parser.parse_args() STORAGE = Path.home() / ".local/share/opencode/storage" -MSG_DIR = STORAGE / "message" / SESSION_ID +MSG_DIR = STORAGE / "message" / args.session_id PART_DIR = STORAGE / "part" if not MSG_DIR.exists():