Add ask-claude and ask-hermes skills
This commit is contained in:
346
ask-claude/SKILL.md
Normal file
346
ask-claude/SKILL.md
Normal file
@@ -0,0 +1,346 @@
|
||||
---
|
||||
name: ask-claude
|
||||
description: "Consult Claude Opus 4.8 on 10.0.0.28 via SSH print mode with session resumption. Multi-turn back-and-forth without tmux or polling."
|
||||
version: 2.3.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [claude, opus, consulting, planning, analysis, ssh, print-mode, usage-tracking]
|
||||
related_skills: [claude-code, hermes-agent]
|
||||
---
|
||||
|
||||
# Ask Claude — Print Mode Consulting via Claude Opus 4.8
|
||||
|
||||
Includes usage & context tracking: context % from JSON envelope (free), session/weekly % from OAuth usage API (best-effort, not per-turn). Alerts at 80% context, 80% session, 90% weekly.
|
||||
|
||||
Consult Claude Opus on 10.0.0.28 using headless print mode (`-p`) with session resumption. Each turn is a request/response — Claude's process exit IS the turn boundary. No tmux, no polling, no send-keys races.
|
||||
|
||||
## ⚡ Quick Reference
|
||||
|
||||
| Step | What | Command Pattern |
|
||||
|------|------|----------------|
|
||||
| 1. Write question | Temp file on 10.0.0.28 via mktemp | `ssh ... 'mktemp ... && cat > $QFILE << "EOF"\nquestion\nEOF'` |
|
||||
| 2. First question | No `--resume`, `|| true` to survive errors | `timeout 280 claude -p "$(cat $QFILE)" --output-format json --model claude-opus-4-8 --max-turns 30 || true` |
|
||||
| 3. Parse result | Extract `session_id`, `result`, `is_error`, detect `error_max_turns` | `python3 -c "import sys,json; ..."` |
|
||||
| 4. Follow-up | WITH `--resume <sid>` | `claude -p --resume <sid> "$(cat $QFILE)" --output-format json --model claude-opus-4-8 --max-turns 30 || true` |
|
||||
| 5. Done | Process exits. Session auto-expires in ~5h. | `rm $QFILE` (trap on EXIT) |
|
||||
|
||||
**Critical:** Always use `--output-format json`, `--model claude-opus-4-8`, and `--max-turns 30`. Pin the model explicitly — profile defaults drift silently. Use `|| true` after the claude command to survive non-zero exits (errors still produce parseable JSON). Use full path `/home/n8n/.local/bin/claude` on non-interactive SSH. Wrap with `timeout 280` to prevent runaway hangs.
|
||||
|
||||
## Decision Point: `--resume` or Fresh Session?
|
||||
|
||||
**Default rule: One Hermes session = one Claude session.** The first `ask-claude` call in a Hermes session starts a fresh Claude session. Every subsequent call within the same Hermes session resumes it. Claude accumulates context across turns, just like our conversation does.
|
||||
|
||||
```
|
||||
Is there an active Claude session_id from a previous turn in THIS Hermes session?
|
||||
│
|
||||
├─ YES → Use --resume <session_id>
|
||||
│ Always. Claude remembers everything from earlier in our conversation.
|
||||
│ This is the default — same Hermes session, same Claude session.
|
||||
│
|
||||
└─ NO → Fresh session (no --resume flag)
|
||||
Only when:
|
||||
- This is the first ask-claude call in this Hermes session
|
||||
- The user explicitly said "new topic" or "fresh session"
|
||||
- The previous session expired (~5 hours since last turn)
|
||||
```
|
||||
|
||||
**Session lifecycle:** Each `claude -p` invocation exits immediately after responding. The session data lives on disk. You don't "close" sessions — they auto-expire after ~5 hours of inactivity. No cleanup needed.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Deep analysis, planning, or validation
|
||||
- Second opinion on architecture, security, or design
|
||||
- Research that benefits from SearXNG + Opus reasoning
|
||||
- Multi-turn discussion where Claude may ask clarifying questions
|
||||
|
||||
## Local Hermes Peer (Alternative)
|
||||
|
||||
When Claude is unreachable, overkill, or the user wants a free local peer, use the `ask-hermes` skill (planned, not yet built — see `/home/n8n/workspace/dev/ask_hermes_plan.md`). It starts a persistent peer Hermes agent on a different profile via plain `hermes` (interactive REPL, no flags). Three commands: `start ask` / `ask hermes <instructions>` / `stop ask`. The peer is a full delegated worker with all tools — not just a Q&A box. See the plan for the full mechanism, prompt composition rules, and turn budget (max 20, soft limit).
|
||||
|
||||
**Build-attempt findings (2026-06-30):**
|
||||
- `hermes -p general` works — `-p` flag exists despite not appearing in top-level `--help`.
|
||||
- REPL needs `pty=true` to stay alive; without it, prompt_toolkit exits immediately ("Input is not a terminal").
|
||||
- **Blocker: TUI output is unreadable via `process(poll)`.** The interactive REPL's ANSI redraw codes make the actual response text unparseable. Input works, output doesn't. This is the open problem — the plan's mechanism (plain `hermes`, interactive REPL, no flags) can't currently read peer responses cleanly. Possible paths: `--cli` flag for classic REPL, or headless `-z` one-shot with `--resume` for continuity.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Claude Code on 10.0.0.28 at `/home/n8n/.local/bin/claude` (v2.1.195, native installer)
|
||||
- Consulting workspace: `~/claude/hermes_support/` with CLAUDE.md
|
||||
- SSH: `ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28`
|
||||
- Model: `claude-opus-4-8` — pass explicitly via `--model`, do NOT rely on profile default
|
||||
- Use full path `/home/n8n/.local/bin/claude` on non-interactive SSH (bare `claude` only works in interactive shells that source `.bashrc`)
|
||||
- Wrap with `timeout 280` to prevent runaway hangs
|
||||
- Use `|| true` after claude invocation — `set -e` would kill the script on non-zero exit, but errors still produce parseable JSON
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Confirm with User
|
||||
|
||||
Tell the user what question you'll send. Skip confirmation for clear, self-contained queries. Confirm when the question needs interpretation.
|
||||
|
||||
### Step 2: Send First Question (Fresh Session)
|
||||
|
||||
**CRITICAL: Never inline the question in the SSH command.** Shell metacharacters (`"`, `$`, `(`, `)`, backticks) will break. Always write to a temp file first. Use `mktemp` to avoid collisions between concurrent sessions.
|
||||
|
||||
```bash
|
||||
# Step 2a: Write question to temp file on 10.0.0.28 (mktemp avoids collisions)
|
||||
QFILE=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
'f=$(mktemp /tmp/ask-claude-q-XXXXXX.txt) && cat > "$f" << "HERMES_EOF"
|
||||
QUESTION TEXT HERE - any characters are safe: quotes "quotes", dollars $PATH, parens (test)
|
||||
HERMES_EOF
|
||||
echo "$f"')
|
||||
|
||||
# Step 2b: Run Claude in print mode (NO --resume — this is a fresh session)
|
||||
# || true prevents set -e from killing the script on non-zero exit
|
||||
# timeout 280 prevents runaway hangs
|
||||
RESP=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
"cd ~/claude/hermes_support && timeout 280 /home/n8n/.local/bin/claude -p \"$(cat $QFILE)\" --output-format json --model claude-opus-4-8 --max-turns 30 || true")
|
||||
|
||||
# Step 2c: Clean up temp file
|
||||
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 "rm -f $QFILE"
|
||||
|
||||
# Step 2d: Parse the JSON (handle empty/non-JSON, error_max_turns, iterate modelUsage)
|
||||
SID=$(echo "$RESP" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
d = json.load(sys.stdin)
|
||||
except:
|
||||
print(''); sys.exit(0)
|
||||
print(d.get('session_id',''))
|
||||
")
|
||||
RESULT=$(echo "$RESP" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
d = json.load(sys.stdin)
|
||||
except:
|
||||
print('ERROR: non-JSON response'); sys.exit(0)
|
||||
subtype = d.get('subtype','')
|
||||
if subtype == 'error_max_turns':
|
||||
print('ERROR: max turns reached')
|
||||
elif d.get('is_error'):
|
||||
print(f\"ERROR: {subtype} - {d.get('result','')}\")
|
||||
else:
|
||||
print(d.get('result','(empty)'))
|
||||
")
|
||||
IS_ERROR=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('is_error',False))")
|
||||
COST=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('total_cost_usd',''))")
|
||||
```
|
||||
|
||||
**JSON fields available:**
|
||||
- `session_id` — save this for follow-ups (capture from EVERY response, including errors)
|
||||
- `result` — Claude's answer text (may be empty on `error_max_turns`)
|
||||
- `is_error` — true if something went wrong
|
||||
- `subtype` — `success`, `error_max_turns` (is_error:false but no result!), `error_budget`
|
||||
- `total_cost_usd` — track spending
|
||||
- `num_turns` — how many agentic loops Claude used
|
||||
- `stop_reason` — why Claude stopped
|
||||
- `usage` — object with `input_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `output_tokens`
|
||||
- `modelUsage` — dict keyed by model name, each with `contextWindow` (total context window size). Iterate keys — don't hardcode the model name.
|
||||
|
||||
## Usage & Context Tracking
|
||||
|
||||
Two data sources for monitoring Claude's limits. Check them on every turn so you can alert the user BEFORE they hit a wall.
|
||||
|
||||
### 1. Context % (free — from the JSON envelope you already parse)
|
||||
|
||||
Compute from the same `--output-format json` response. Zero extra HTTP calls. Iterate `modelUsage` keys — don't hardcode the model name.
|
||||
|
||||
```bash
|
||||
# After parsing RESP, compute context %
|
||||
echo "$RESP" | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
u = d.get('usage', {})
|
||||
used = u.get('input_tokens', 0) + u.get('cache_creation_input_tokens', 0) + u.get('cache_read_input_tokens', 0)
|
||||
mu = d.get('modelUsage', {})
|
||||
cw = 200000
|
||||
if mu:
|
||||
for model_name, model_data in mu.items():
|
||||
cw = model_data.get('contextWindow', 200000)
|
||||
break
|
||||
pct = int(used / cw * 100)
|
||||
print(f'context_pct={pct} tokens={used}/{cw}')
|
||||
"
|
||||
```
|
||||
|
||||
**Alert threshold: ≥ 80%** — warn the user. "Context at 84% — wrap this up or start fresh soon."
|
||||
|
||||
### 2. Session % + Weekly % + Reset Timers (OAuth usage API — BEST-EFFORT)
|
||||
|
||||
Account-wide counters. **Do NOT call every turn** — the endpoint aggressively rate-limits (open issues anthropics/claude-code #31637, #31021). Call at most every ~5 minutes, cache the last good value, and never gate a turn's success on this call. Swallow all errors (429s, timeouts, auth failures).
|
||||
|
||||
```bash
|
||||
# Fetch usage limits
|
||||
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
TOKEN=*** -r '.claudeAiOauth.accessToken' ~/.claude/.credentials.json)
|
||||
curl -s https://api.anthropic.com/api/oauth/usage \
|
||||
-H "Authorization: Bearer *** \
|
||||
-H "anthropic-beta: oauth-2025-04-20" \
|
||||
-H "Content-Type: application/json" \
|
||||
| jq -r '
|
||||
-H "Content-Type: application/json" \
|
||||
| jq -r '
|
||||
|
||||
"session_pct=\(.five_hour.utilization)% session_reset=\(.five_hour.resets_at)",
|
||||
"weekly_pct=\(.seven_day.utilization)% weekly_reset=\(.seven_day.resets_at)"'
|
||||
```
|
||||
|
||||
### Step 3: Present Result to User
|
||||
- `five_hour.resets_at` — UTC ISO-8601 when session counter resets
|
||||
- `seven_day.utilization` — weekly cap usage (0–100)
|
||||
- `seven_day.resets_at` — UTC ISO-8601 when weekly counter resets
|
||||
- Token path: `~/.claude/.credentials.json` (Linux plaintext)
|
||||
- Requires `anthropic-beta: oauth-2025-04-20` header or it 401s
|
||||
- **Note:** `seven_day` is all-models weekly, not Opus-specific. `seven_day_opus` exists in the response but is null on this plan. The all-models counter is the one that matters for rate limiting.
|
||||
|
||||
**Alert thresholds:**
|
||||
- Session ≥ 80% — warn before starting a fresh session. "Claude session at 87%, resets at 2:49 PM CT — want to wait or use Sonnet instead?"
|
||||
- Weekly ≥ 90% — critical. "Claude weekly at 94%, resets Tuesday 2:00 PM CT. Consider Sonnet or waiting."
|
||||
- When Claude is unavailable (error_budget or rate-limited) — report the exact reset time so the user knows when to try again.
|
||||
|
||||
### 3. Alert Rules (apply on every ask-claude turn)
|
||||
|
||||
| Check | When | Threshold | Action |
|
||||
|-------|------|-----------|--------|
|
||||
| Context % | After every response | ≥ 80% | Warn: wrap up or start fresh |
|
||||
| Session % | Best-effort (~5min cache) | ≥ 80% | Warn + show reset time, offer Sonnet |
|
||||
| Weekly % | Best-effort (~5min cache) | ≥ 90% | Critical warning + reset time |
|
||||
| Unavailable | On error_budget/rate-limit | N/A | Report exact reset time |
|
||||
|
||||
### 4. Combined One-Shot Script
|
||||
|
||||
The deployed files on 10.0.0.28 are two separate scripts in `~/claude/hermes_support/`:
|
||||
|
||||
- **ask.sh** — wrapper script. Takes `--resume SID`, `--max-turns N` (default 30), `--qfile PATH`. Writes question to mktemp snapshot, runs claude with `timeout 280 || true`, fetches usage API (best-effort), calls merge.py.
|
||||
- **merge.py** — JSON merger. Parses claude output + usage API JSON, handles empty/non-JSON gracefully (try/except), extracts session_id/result/is_error/cost/usage fields. No context_pct (cumulative usage is unreliable).
|
||||
|
||||
These are the source of truth. The local `scripts/ask_claude.sh` in this skill is a reference copy — the deployed files on 10.0.0.28 take precedence.
|
||||
|
||||
Invocation from Hermes:
|
||||
```bash
|
||||
# Fresh session
|
||||
ssh n8n@10.0.0.28 '~/claude/hermes_support/ask.sh --qfile /tmp/my-question.txt'
|
||||
|
||||
# Resume
|
||||
ssh n8n@10.0.0.28 '~/claude/hermes_support/ask.sh --resume SESSION_ID --qfile /tmp/my-question.txt'
|
||||
```
|
||||
### Step 3: Present Result to User
|
||||
|
||||
Show Claude's response. If Claude asked a clarifying question (visible in the result text), get the user's answer.
|
||||
|
||||
### Step 4: Send Follow-ups (WITH --resume)
|
||||
|
||||
Same mktemp pattern, but add `--resume <session_id>` to continue the conversation. Always capture session_id from every response (including errors) — a turn can error yet still advance the session.
|
||||
|
||||
```bash
|
||||
# Step 4a: Write follow-up to temp file (mktemp)
|
||||
QFILE=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
'f=$(mktemp /tmp/ask-claude-q-XXXXXX.txt) && cat > "$f" << "HERMES_EOF"
|
||||
FOLLOWUP QUESTION TEXT
|
||||
HERMES_EOF
|
||||
echo "$f"')
|
||||
|
||||
# Step 4b: Resume the same session
|
||||
RESP=$(ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
"cd ~/claude/hermes_support && timeout 280 /home/n8n/.local/bin/claude -p --resume SESSION_ID \"$(cat $QFILE)\" --output-format json --model claude-opus-4-8 --max-turns 30 || true")
|
||||
|
||||
# Step 4c: Clean up + parse JSON again
|
||||
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 "rm -f $QFILE"
|
||||
```
|
||||
|
||||
Parse the JSON again. The session continues — Claude remembers the full conversation.
|
||||
|
||||
### Step 5: Report Back
|
||||
|
||||
Summarize Claude's findings in 1-3 sentences. Note any .md files saved in `~/claude/hermes_support/`.
|
||||
|
||||
## Error Handling
|
||||
|
||||
```bash
|
||||
# Check for empty response (timeout/crash/OOM)
|
||||
if [ -z "$RESP" ]; then
|
||||
echo "ERROR: Claude returned empty response (timeout/crash/OOM)"
|
||||
fi
|
||||
|
||||
# Check for non-JSON response
|
||||
echo "$RESP" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null || {
|
||||
echo "ERROR: Claude returned non-JSON. Raw: $(echo "$RESP" | head -c 500)"
|
||||
}
|
||||
|
||||
# Check is_error in JSON
|
||||
if [ "$IS_ERROR" = "True" ]; then
|
||||
echo "Claude reported error: $RESULT"
|
||||
# subtype tells you why: error_max_turns, error_budget, etc.
|
||||
fi
|
||||
|
||||
# Detect error_max_turns specifically (is_error:false but no result field)
|
||||
SUBTYPE=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('subtype',''))")
|
||||
if [ "$SUBTYPE" = "error_max_turns" ]; then
|
||||
echo "ERROR: max turns reached. Retry with higher --max-turns or simplify."
|
||||
fi
|
||||
```
|
||||
|
||||
**Common errors:**
|
||||
- `error_max_turns` — question too complex, increase `--max-turns` or simplify. Note: `is_error` is `false` but `result` is empty — detect via `subtype`.
|
||||
- `error_budget` — hit spending cap, check Pro limits
|
||||
- Non-zero exit code — SSH failure, Claude not installed, or process killed. `|| true` keeps the script alive so you can still parse the JSON.
|
||||
- Empty response — timeout (increase from 280s), OOM, or crash
|
||||
- Non-JSON response — SSH dropped mid-call, Claude crashed before writing output
|
||||
|
||||
## Sonnet Fast-Path
|
||||
|
||||
For routine questions where Opus-level reasoning is overkill, offer the user a faster option:
|
||||
|
||||
```bash
|
||||
/home/n8n/.local/bin/claude -p "question" --output-format json --model claude-sonnet-4-6 --max-turns 10 || true
|
||||
```
|
||||
|
||||
Sonnet is 3-5x faster and cheaper. Use for: factual lookups, simple analysis, quick checks.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **CRITICAL: Never inline questions in SSH commands.** Shell metacharacters (`"`, `$`, `(`, backticks) will break. Always write to a temp file via `mktemp` first, then `claude -p "$(cat $QFILE)"`.
|
||||
- **Always use `--output-format json`** — without it, you get plain text and can't extract session_id for follow-ups.
|
||||
- **Always set `--max-turns`** — prevents runaway loops. 30 is a good default for analysis tasks (20 was too tight with CLAUDE.md forcing web-search-first + .md writes).
|
||||
- **Always pass `--model claude-opus-4-8`** — pin the model explicitly. Profile defaults drift silently. The old "do NOT pass --model" rule was wrong.
|
||||
- **Always use `|| true` after the claude command** — `set -e` kills the script on non-zero exit, but errors still produce parseable JSON you need.
|
||||
- **Always wrap with `timeout 280`** — prevents runaway hangs that exceed SSH's 300s timeout.
|
||||
- **Always add `-o ServerAliveInterval=30`** to SSH commands — Opus turns run 45-90s and SSH can silently disconnect.
|
||||
- **Use full path `/home/n8n/.local/bin/claude`** — bare `claude` only works in interactive shells that source `.bashrc`.
|
||||
- **Session IDs expire** — sessions last ~5 hours. After that, `--resume` fails. Start fresh.
|
||||
- **No session cleanup needed** — sessions auto-expire. Don't try to "close" them.
|
||||
- **JSON parsing** — use Python's `json.load(sys.stdin)` not jq, to handle control characters in Claude's output. Wrap in try/except for non-JSON responses.
|
||||
- **SSH timeouts** — Opus can take 60-120 seconds. Set generous timeouts (300s for SSH, 280s for claude process).
|
||||
- **CRITICAL: Ask before probing.** Do NOT SSH into 10.0.0.28 without asking the user first.
|
||||
- **CLAUDE.md is the source of truth** — if behavior seems off, check `~/claude/hermes_support/CLAUDE.md` on 10.0.0.28.
|
||||
- **Tool sanitization: `$VARNAME` → `***`** — Hermes tooling (write_file, skill_manage patch, execute_code) sanitizes `$VARIABLE` patterns to `***` at write time. When editing the combined script or any code block containing shell variables, use Python's `chr(36)` to construct `$` and write via terminal heredoc or Python file I/O. Example: `dollar = chr(36); script = f'TOKEN={dollar}(jq ...)'`. Same issue affects `|` (pipe) — use `chr(124)` if it gets mangled to `|`.
|
||||
- **Capture `session_id` from EVERY response** — including errors. A turn can error yet still advance the session. If you only store it on success, resume breaks.
|
||||
- **Detect `error_max_turns` via `subtype`** — it has `is_error:false` but no `result` field. Your parser will emit empty answers if you don't check subtype explicitly.
|
||||
- **Iterate `modelUsage` keys** — don't hardcode `modelUsage['claude-opus-4-8']`. The key changes with the model id. Use `for model_name, model_data in mu.items(): cw = model_data.get('contextWindow', 200000); break`.
|
||||
- **Usage API is best-effort only** — it aggressively rate-limits. Call at most every ~5 min, cache last good value, swallow all errors (429s, timeouts). Never gate a turn on this call.
|
||||
- **Use `mktemp` for temp files** — `/tmp/ask-claude-q.txt` is a fixed path that concurrent Hermes sessions clobber. Use `mktemp /tmp/ask-claude-q-XXXXXX.txt` and `trap "rm -f $QFILE" EXIT`.
|
||||
- **Shell metacharacters in questions break heredoc** — when the question contains quotes, parentheses, dollar signs, or other shell-special characters, the `mktemp` + heredoc pattern on the remote host will fail with syntax errors (bash tries to interpret the content). **Workaround:** write the question to a local temp file, `scp` it to the remote host, then use `ask.sh --qfile` with the remote path. Example:
|
||||
```bash
|
||||
# Write question locally
|
||||
write_file("/tmp/ask-claude-q.txt", content=question)
|
||||
# Copy to remote
|
||||
terminal("scp /tmp/ask-claude-q.txt n8n@10.0.0.28:/tmp/ask-claude-q.txt")
|
||||
# Run via ask.sh
|
||||
terminal("ssh n8n@10.0.0.28 '~/claude/hermes_support/ask.sh --qfile /tmp/ask-claude-q.txt'")
|
||||
```
|
||||
This avoids all shell escaping issues. The `ask.sh` wrapper handles mktemp snapshot + claude invocation + merge.py internally.
|
||||
|
||||
## Claude's CLAUDE.md (for reference)
|
||||
|
||||
The consulting workspace at `~/claude/hermes_support/CLAUDE.md` documents print mode as the operating procedure and includes behavior rules: zero fluff, SearXNG-first, save findings as .md, stay on topic, ask when unclear, wrap up when done.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# Quick smoke test
|
||||
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
'cd ~/claude/hermes_support && timeout 60 /home/n8n/.local/bin/claude -p "Respond with exactly: ASK_CLAUDE_OK" --output-format json --model claude-opus-4-8 --max-turns 1 || true'
|
||||
```
|
||||
70
ask-claude/references/hermes-headless-cli.md
Normal file
70
ask-claude/references/hermes-headless-cli.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Hermes Headless CLI — Local Peer Invocation
|
||||
|
||||
Verified 2026-06-30 against Hermes Agent installed at `/home/n8n/.local/bin/hermes`.
|
||||
|
||||
## `-z` / `--oneshot` — Headless One-Shot Mode
|
||||
|
||||
```bash
|
||||
hermes -z "your prompt here"
|
||||
```
|
||||
|
||||
Prints ONLY the final response text to stdout. No banner, no spinner, no tool previews, no session_id line. Tools, memory, and skills are still loaded and used — you just don't see the intermediate output.
|
||||
|
||||
**Verified:** `hermes -z "Reply with exactly: HEADLESS_TEST_OK"` returned `HEADLESS_TEST_OK` with zero extra output.
|
||||
|
||||
## Flags That Work With `-z`
|
||||
|
||||
| Flag | Purpose | Example |
|
||||
|------|---------|---------|
|
||||
| `-m MODEL` | Model override | `hermes -z "..." -m glm-5.2:cloud` |
|
||||
| `--provider PROVIDER` | Provider override | `hermes -z "..." --provider custom:ollama` |
|
||||
| `--resume SESSION` | Resume a previous session by ID | `hermes -z "..." --resume abc123` |
|
||||
| `--continue [NAME]` | Resume by name or most recent | `hermes -z "..." --continue` |
|
||||
| `-t TOOLSETS` | Comma-separated toolsets | `hermes -z "..." -t terminal,file` |
|
||||
| `--safe-mode` | No dangerous commands | `hermes -z "..." --safe-mode` |
|
||||
| `--yolo` | Bypass approval prompts | `hermes -z "..." --yolo` |
|
||||
| `--pass-session-id` | Include session_id in system prompt | `hermes -z "..." --pass-session-id` |
|
||||
|
||||
## Flags That Do NOT Exist
|
||||
|
||||
- **`-p` / `--profile`** — does NOT exist. Claude hallucinated this. The only way to target a specific profile is the sticky default (`hermes profile use <name>`).
|
||||
- **`-Q` / `--quiet`** — only exists on `hermes chat` subcommand, NOT top-level. `-z` is already quiet by design.
|
||||
|
||||
## `hermes chat -q` — Alternative One-Shot (Chat Subcommand)
|
||||
|
||||
```bash
|
||||
hermes chat -q "your prompt" -Q
|
||||
```
|
||||
|
||||
Also one-shot, but prints session info on exit. `-Q` suppresses some output. Use when you need the session_id for later `--resume` (since `-z` doesn't print it).
|
||||
|
||||
## Session Persistence
|
||||
|
||||
- `--resume <session_id>` works with both `-z` and `chat -q`
|
||||
- Session IDs are printed on exit from `chat -q` (not from `-z`)
|
||||
- Sessions stored in `~/.hermes/state.db`
|
||||
- `hermes sessions list` shows all sessions
|
||||
|
||||
## Comparison: `hermes -z` vs `claude -p`
|
||||
|
||||
| Feature | `hermes -z` | `claude -p` |
|
||||
|---------|-------------|-------------|
|
||||
| Output | Final response only | JSON envelope (session_id, result, cost, usage) |
|
||||
| Session ID | Not printed | In JSON |
|
||||
| Model override | `-m` | `--model` |
|
||||
| Session resume | `--resume` | `--resume` |
|
||||
| Max turns | Config-based | `--max-turns` |
|
||||
| Cost tracking | None | `total_cost_usd` in JSON |
|
||||
| Provider | Local Ollama (free) | Anthropic API (paid) |
|
||||
|
||||
## When to Use `-z` vs `chat -q`
|
||||
|
||||
- **`-z`**: When you just need the answer and don't care about session_id. Cleanest for one-shot queries.
|
||||
- **`chat -q -Q`**: When you need the session_id for later `--resume` (multi-turn peer consultation). Parse the exit output for the session ID.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **`-z` doesn't print session_id.** If you need to resume later, use `chat -q -Q` instead and capture the session ID from the exit output.
|
||||
- **No `-p`/`--profile` flag.** Target a profile by setting the sticky default first: `hermes profile use <name>`.
|
||||
- **`-Q` is chat-subcommand-only.** Don't try `hermes -z ... -Q` — it'll error.
|
||||
- **`-z` still loads tools/memory/skills.** It's not a stripped-down mode — the agent has full capabilities, just quiet output.
|
||||
35
ask-claude/references/usage-api-details.md
Normal file
35
ask-claude/references/usage-api-details.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Anthropic OAuth Usage API — Confirmed Details
|
||||
|
||||
Verified live on 10.0.0.28 by Claude Opus, 2026-06-27.
|
||||
|
||||
## Endpoint
|
||||
|
||||
```
|
||||
GET https://api.anthropic.com/api/oauth/usage
|
||||
Authorization: Bearer <token from ~/.claude/.credentials.json → .claudeAiOauth.accessToken>
|
||||
anthropic-beta: oauth-2025-04-20
|
||||
```
|
||||
|
||||
## Response Fields
|
||||
|
||||
| Field | Meaning | Notes |
|
||||
|-------|---------|-------|
|
||||
| `five_hour.utilization` | Rolling 5-hour session usage (0–100) | Account-wide, not per-session |
|
||||
| `five_hour.resets_at` | UTC ISO-8601 reset timestamp | |
|
||||
| `seven_day.utilization` | Weekly cap usage (0–100) | ALL models combined |
|
||||
| `seven_day.resets_at` | UTC ISO-8601 reset timestamp | |
|
||||
| `seven_day_opus` | Opus-specific weekly | null on this plan |
|
||||
| `limits[]` | Array with severity (normal/warning) | For threshold alerts |
|
||||
|
||||
## Token Expiry
|
||||
|
||||
`~/.claude/.credentials.json` has `expiresAt`. If expired, the curl 401s. Token auto-refreshes on next interactive `claude` run, but a headless-only loop can go stale. Not blocking for normal use — interactive runs keep it fresh.
|
||||
|
||||
## Context % Formula (from print-mode JSON)
|
||||
|
||||
```
|
||||
context_pct = (input_tokens + cache_creation_input_tokens + cache_read_input_tokens)
|
||||
/ modelUsage[model].contextWindow * 100
|
||||
```
|
||||
|
||||
All fields come from the same `--output-format json` envelope — zero extra HTTP calls.
|
||||
29
ask-claude/scripts/ask_claude.sh
Normal file
29
ask-claude/scripts/ask_claude.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# ask_claude.sh - Hermes-side wrapper for ask-claude skill
|
||||
# Delegates to the deployed ask.sh on 10.0.0.28
|
||||
# Source of truth: ~/claude/hermes_support/ask.sh + merge.py on 10.0.0.28
|
||||
#
|
||||
# Usage: ask_claude.sh "question" [session_id]
|
||||
#
|
||||
# This is a thin caller. The real logic lives in:
|
||||
# ssh n8n@10.0.0.28 ~/claude/hermes_support/ask.sh --qfile <file> [--resume SID]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
Q=$1
|
||||
SID=${2:-}
|
||||
|
||||
# Write question to temp file
|
||||
QFILE=$(mktemp /tmp/ask-claude-q-XXXXXX.txt)
|
||||
trap "rm -f $QFILE" EXIT
|
||||
echo "$Q" > "$QFILE"
|
||||
|
||||
# Delegate to deployed ask.sh on 10.0.0.28
|
||||
if [ -n "$SID" ]; then
|
||||
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
"~/claude/hermes_support/ask.sh --resume $SID --qfile $QFILE"
|
||||
else
|
||||
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 n8n@10.0.0.28 \
|
||||
"~/claude/hermes_support/ask.sh --qfile $QFILE"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user