diff --git a/README.md b/README.md index dd961c9a..bd7f9145 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,18 @@ Requires [codex CLI](https://github.com/openai/codex) for review. See the full [ /humanize:start-rlcr-loop docs/plan.md ``` -4. **Monitor progress**: +4. **Consult Gemini** for deep web research (requires Gemini CLI): + ```bash + /humanize:ask-gemini What are the latest best practices for X? + ``` + +5. **Monitor progress**: ```bash source /scripts/humanize.sh - humanize monitor rlcr + humanize monitor rlcr # RLCR loop + humanize monitor skill # All skill invocations (codex + gemini) + humanize monitor codex # Codex invocations only + humanize monitor gemini # Gemini invocations only ``` ## Monitor Dashboard diff --git a/scripts/ask-codex.sh b/scripts/ask-codex.sh index ac26fc32..bea40b4e 100755 --- a/scripts/ask-codex.sh +++ b/scripts/ask-codex.sh @@ -234,6 +234,7 @@ $QUESTION - Effort: $CODEX_EFFORT - Timeout: ${CODEX_TIMEOUT}s - Timestamp: $TIMESTAMP +- Tool: codex EOF # ======================================== @@ -317,6 +318,7 @@ if [[ $CODEX_EXIT_CODE -eq 124 ]]; then # Save metadata even on timeout cat > "$SKILL_DIR/metadata.md" << EOF --- +tool: codex model: $CODEX_MODEL effort: $CODEX_EFFORT timeout: $CODEX_TIMEOUT @@ -343,6 +345,7 @@ if [[ $CODEX_EXIT_CODE -ne 0 ]]; then # Save metadata cat > "$SKILL_DIR/metadata.md" << EOF --- +tool: codex model: $CODEX_MODEL effort: $CODEX_EFFORT timeout: $CODEX_TIMEOUT @@ -368,6 +371,7 @@ if [[ ! -s "$CODEX_STDOUT_FILE" ]]; then cat > "$SKILL_DIR/metadata.md" << EOF --- +tool: codex model: $CODEX_MODEL effort: $CODEX_EFFORT timeout: $CODEX_TIMEOUT @@ -390,6 +394,7 @@ cp "$CODEX_STDOUT_FILE" "$SKILL_DIR/output.md" # Save metadata cat > "$SKILL_DIR/metadata.md" << EOF --- +tool: codex model: $CODEX_MODEL effort: $CODEX_EFFORT timeout: $CODEX_TIMEOUT diff --git a/scripts/ask-gemini.sh b/scripts/ask-gemini.sh new file mode 100755 index 00000000..489bc4db --- /dev/null +++ b/scripts/ask-gemini.sh @@ -0,0 +1,385 @@ +#!/usr/bin/env bash +# +# Ask Gemini - One-shot consultation with Gemini CLI +# +# Sends a question or task to gemini in non-interactive mode and returns +# the response. Gemini is always instructed to leverage Google Search +# for deep web research. +# +# Usage: +# ask-gemini.sh [--gemini-model MODEL] [--gemini-timeout SECONDS] [question...] +# +# Output: +# stdout: Gemini's response (for Claude to read) +# stderr: Status/debug info (model, log paths) +# +# Storage: +# Project-local: .humanize/skill//{input,output,metadata}.md +# Cache: ~/.cache/humanize//skill-/gemini-run.{cmd,out,log} +# + +set -euo pipefail + +# ======================================== +# Source Shared Libraries +# ======================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" + +# Source portable timeout wrapper +source "$SCRIPT_DIR/portable-timeout.sh" + +# ======================================== +# Default Configuration +# ======================================== + +DEFAULT_GEMINI_MODEL="gemini-3.1-pro-preview" +DEFAULT_ASK_GEMINI_TIMEOUT=3600 + +GEMINI_MODEL="$DEFAULT_GEMINI_MODEL" +GEMINI_TIMEOUT="$DEFAULT_ASK_GEMINI_TIMEOUT" + +# ======================================== +# Help +# ======================================== + +show_help() { + cat << 'HELP_EOF' +ask-gemini - One-shot deep-research consultation with Gemini + +USAGE: + /humanize:ask-gemini [OPTIONS] + +OPTIONS: + --gemini-model + Gemini model name (default: gemini-3.1-pro-preview) + --gemini-timeout + Timeout for the Gemini query in seconds (default: 3600) + -h, --help Show this help message + +DESCRIPTION: + Sends a one-shot question or task to the Gemini CLI in non-interactive + mode (-p). The prompt is augmented with an instruction to perform web + research via Google Search, making this ideal for deep-research tasks + that benefit from up-to-date internet information. + + The response is saved to .humanize/skill//output.md for reference. + +EXAMPLES: + /humanize:ask-gemini What are the latest best practices for Rust error handling? + /humanize:ask-gemini --gemini-model gemini-2.5-pro Review recent CVEs for OpenSSL 3.x + /humanize:ask-gemini --gemini-timeout 600 Compare React Server Components vs Astro Islands + +ENVIRONMENT: + HUMANIZE_GEMINI_YOLO + Set to "true" or "1" to auto-approve all Gemini tool calls (--yolo). + Default behaviour uses --sandbox mode. +HELP_EOF + exit 0 +} + +# ======================================== +# Parse Arguments +# ======================================== + +QUESTION_PARTS=() +OPTIONS_DONE=false + +while [[ $# -gt 0 ]]; do + if [[ "$OPTIONS_DONE" == "true" ]]; then + QUESTION_PARTS+=("$1") + shift + continue + fi + case $1 in + -h|--help) + show_help + ;; + --) + OPTIONS_DONE=true + shift + ;; + --gemini-model) + if [[ -z "${2:-}" ]]; then + echo "Error: --gemini-model requires a MODEL argument" >&2 + exit 1 + fi + GEMINI_MODEL="$2" + shift 2 + ;; + --gemini-timeout) + if [[ -z "${2:-}" ]]; then + echo "Error: --gemini-timeout requires a number argument (seconds)" >&2 + exit 1 + fi + if ! [[ "$2" =~ ^[0-9]+$ ]]; then + echo "Error: --gemini-timeout must be a positive integer (seconds), got: $2" >&2 + exit 1 + fi + GEMINI_TIMEOUT="$2" + shift 2 + ;; + -*) + echo "Error: Unknown option: $1" >&2 + echo "Use --help for usage information" >&2 + exit 1 + ;; + *) + QUESTION_PARTS+=("$1") + OPTIONS_DONE=true + shift + ;; + esac +done + +# Join question parts into a single string +QUESTION="${QUESTION_PARTS[*]}" + +# ======================================== +# Validate Prerequisites +# ======================================== + +if ! command -v gemini &>/dev/null; then + echo "Error: 'gemini' command is not installed or not in PATH" >&2 + echo "" >&2 + echo "Please install Gemini CLI: npm install -g @anthropic-ai/gemini-cli or https://github.com/anthropics/gemini-cli" >&2 + echo "Then retry: /humanize:ask-gemini " >&2 + exit 1 +fi + +if [[ -z "$QUESTION" ]]; then + echo "Error: No question or task provided" >&2 + echo "" >&2 + echo "Usage: /humanize:ask-gemini [OPTIONS] " >&2 + echo "" >&2 + echo "For help: /humanize:ask-gemini --help" >&2 + exit 1 +fi + +# Validate model name for safety (alphanumeric, hyphen, underscore, dot) +if [[ ! "$GEMINI_MODEL" =~ ^[a-zA-Z0-9._-]+$ ]]; then + echo "Error: Gemini model contains invalid characters" >&2 + echo " Model: $GEMINI_MODEL" >&2 + echo " Only alphanumeric, hyphen, underscore, dot allowed" >&2 + exit 1 +fi + +# ======================================== +# Detect Project Root +# ======================================== + +if git rev-parse --show-toplevel &>/dev/null; then + PROJECT_ROOT=$(git rev-parse --show-toplevel) +else + PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}" +fi + +# ======================================== +# Create Storage Directories +# ======================================== + +TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S) +UNIQUE_ID="${TIMESTAMP}-$$-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')" + +# Project-local storage: .humanize/skill// +SKILL_DIR="$PROJECT_ROOT/.humanize/skill/$UNIQUE_ID" +mkdir -p "$SKILL_DIR" + +# Cache storage: ~/.cache/humanize//skill-/ +SANITIZED_PROJECT_PATH=$(echo "$PROJECT_ROOT" | sed 's/[^a-zA-Z0-9._-]/-/g' | sed 's/--*/-/g') +CACHE_BASE="${XDG_CACHE_HOME:-$HOME/.cache}" +CACHE_DIR="$CACHE_BASE/humanize/$SANITIZED_PROJECT_PATH/skill-$UNIQUE_ID" +if ! mkdir -p "$CACHE_DIR" 2>/dev/null; then + CACHE_DIR="$SKILL_DIR/cache" + mkdir -p "$CACHE_DIR" + echo "ask-gemini: warning: home cache not writable, using $CACHE_DIR" >&2 +fi + +# ======================================== +# Save Input +# ======================================== + +cat > "$SKILL_DIR/input.md" << EOF +# Ask Gemini Input + +## Question + +$QUESTION + +## Configuration + +- Model: $GEMINI_MODEL +- Timeout: ${GEMINI_TIMEOUT}s +- Timestamp: $TIMESTAMP +- Tool: gemini +EOF + +# ======================================== +# Build Gemini Command +# ======================================== + +GEMINI_ARGS=("-m" "$GEMINI_MODEL") + +# Determine approval mode +if [[ "${HUMANIZE_GEMINI_YOLO:-}" == "true" ]] || [[ "${HUMANIZE_GEMINI_YOLO:-}" == "1" ]]; then + GEMINI_ARGS+=("--yolo") +else + GEMINI_ARGS+=("--sandbox") +fi + +# Use text output format for clean stdout +GEMINI_ARGS+=("-o" "text") + +# Build the augmented prompt with web-search instruction +AUGMENTED_PROMPT="You MUST use Google Search to find the most up-to-date and accurate information before answering. Perform thorough web research. Cite sources where possible. + +--- + +$QUESTION" + +# ======================================== +# Save Debug Command +# ======================================== + +GEMINI_CMD_FILE="$CACHE_DIR/gemini-run.cmd" +GEMINI_STDOUT_FILE="$CACHE_DIR/gemini-run.out" +GEMINI_STDERR_FILE="$CACHE_DIR/gemini-run.log" + +{ + echo "# Gemini ask-gemini invocation debug info" + echo "# Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "# Working directory: $PROJECT_ROOT" + echo "# Timeout: $GEMINI_TIMEOUT seconds" + echo "" + echo "gemini ${GEMINI_ARGS[*]} -p \"\"" + echo "" + echo "# Prompt content:" + echo "$AUGMENTED_PROMPT" +} > "$GEMINI_CMD_FILE" + +# ======================================== +# Run Gemini +# ======================================== + +echo "ask-gemini: model=$GEMINI_MODEL timeout=${GEMINI_TIMEOUT}s" >&2 +echo "ask-gemini: cache=$CACHE_DIR" >&2 +echo "ask-gemini: running gemini -p ..." >&2 + +# Portable epoch-to-ISO8601 formatter +epoch_to_iso() { + local epoch="$1" + date -u -d "@$epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || + date -u -r "$epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || + echo "unknown" +} + +START_TIME=$(date +%s) + +GEMINI_EXIT_CODE=0 +run_with_timeout "$GEMINI_TIMEOUT" gemini "${GEMINI_ARGS[@]}" -p "$AUGMENTED_PROMPT" \ + > "$GEMINI_STDOUT_FILE" 2> "$GEMINI_STDERR_FILE" || GEMINI_EXIT_CODE=$? + +END_TIME=$(date +%s) +DURATION=$((END_TIME - START_TIME)) + +echo "ask-gemini: exit_code=$GEMINI_EXIT_CODE duration=${DURATION}s" >&2 + +# ======================================== +# Handle Results +# ======================================== + +if [[ $GEMINI_EXIT_CODE -eq 124 ]]; then + echo "Error: Gemini timed out after ${GEMINI_TIMEOUT} seconds" >&2 + echo "" >&2 + echo "Try increasing the timeout:" >&2 + echo " /humanize:ask-gemini --gemini-timeout $((GEMINI_TIMEOUT * 2)) " >&2 + echo "" >&2 + echo "Debug logs: $CACHE_DIR" >&2 + + cat > "$SKILL_DIR/metadata.md" << EOF +--- +tool: gemini +model: $GEMINI_MODEL +timeout: $GEMINI_TIMEOUT +exit_code: 124 +duration: ${DURATION}s +status: timeout +started_at: $(epoch_to_iso "$START_TIME") +--- +EOF + exit 124 +fi + +if [[ $GEMINI_EXIT_CODE -ne 0 ]]; then + echo "Error: Gemini exited with code $GEMINI_EXIT_CODE" >&2 + if [[ -s "$GEMINI_STDERR_FILE" ]]; then + echo "" >&2 + echo "Gemini stderr (last 20 lines):" >&2 + tail -20 "$GEMINI_STDERR_FILE" >&2 + fi + echo "" >&2 + echo "Debug logs: $CACHE_DIR" >&2 + + cat > "$SKILL_DIR/metadata.md" << EOF +--- +tool: gemini +model: $GEMINI_MODEL +timeout: $GEMINI_TIMEOUT +exit_code: $GEMINI_EXIT_CODE +duration: ${DURATION}s +status: error +started_at: $(epoch_to_iso "$START_TIME") +--- +EOF + exit "$GEMINI_EXIT_CODE" +fi + +if [[ ! -s "$GEMINI_STDOUT_FILE" ]]; then + echo "Error: Gemini returned empty response" >&2 + if [[ -s "$GEMINI_STDERR_FILE" ]]; then + echo "" >&2 + echo "Gemini stderr (last 20 lines):" >&2 + tail -20 "$GEMINI_STDERR_FILE" >&2 + fi + echo "" >&2 + echo "Debug logs: $CACHE_DIR" >&2 + + cat > "$SKILL_DIR/metadata.md" << EOF +--- +tool: gemini +model: $GEMINI_MODEL +timeout: $GEMINI_TIMEOUT +exit_code: 0 +duration: ${DURATION}s +status: empty_response +started_at: $(epoch_to_iso "$START_TIME") +--- +EOF + exit 1 +fi + +# ======================================== +# Save Output and Metadata +# ======================================== + +cp "$GEMINI_STDOUT_FILE" "$SKILL_DIR/output.md" + +cat > "$SKILL_DIR/metadata.md" << EOF +--- +tool: gemini +model: $GEMINI_MODEL +timeout: $GEMINI_TIMEOUT +exit_code: 0 +duration: ${DURATION}s +status: success +started_at: $(epoch_to_iso "$START_TIME") +--- +EOF + +echo "ask-gemini: response saved to $SKILL_DIR/output.md" >&2 + +# ======================================== +# Output Response +# ======================================== + +cat "$GEMINI_STDOUT_FILE" diff --git a/scripts/humanize.sh b/scripts/humanize.sh index a3492844..c5ac3f20 100755 --- a/scripts/humanize.sh +++ b/scripts/humanize.sh @@ -1182,13 +1182,21 @@ humanize() { skill) _humanize_monitor_skill "$@" ;; + codex) + _humanize_monitor_skill --tool-filter codex "$@" + ;; + gemini) + _humanize_monitor_skill --tool-filter gemini "$@" + ;; *) - echo "Usage: humanize monitor " + echo "Usage: humanize monitor " echo "" echo "Subcommands:" echo " rlcr Monitor the latest RLCR loop log from .humanize/rlcr" echo " pr Monitor the latest PR loop from .humanize/pr-loop" - echo " skill Monitor ask-codex skill invocations from .humanize/skill" + echo " skill Monitor all skill invocations (codex + gemini)" + echo " codex Monitor ask-codex skill invocations only" + echo " gemini Monitor ask-gemini skill invocations only" echo "" echo "Features:" echo " - Fixed status bar showing session info, round progress, model config" @@ -1205,7 +1213,9 @@ humanize() { echo "Commands:" echo " monitor rlcr Monitor the latest RLCR loop log" echo " monitor pr Monitor the latest PR loop" - echo " monitor skill Monitor ask-codex skill invocations" + echo " monitor skill Monitor all skill invocations (codex + gemini)" + echo " monitor codex Monitor ask-codex skill invocations only" + echo " monitor gemini Monitor ask-gemini skill invocations only" return 1 ;; esac diff --git a/scripts/lib/monitor-skill.sh b/scripts/lib/monitor-skill.sh index 218fab92..8803f139 100644 --- a/scripts/lib/monitor-skill.sh +++ b/scripts/lib/monitor-skill.sh @@ -3,15 +3,18 @@ # monitor-skill.sh - Skill monitor for humanize # # Provides the _humanize_monitor_skill function for monitoring -# ask-codex skill invocations from .humanize/skill directory. +# skill invocations (ask-codex, ask-gemini) from .humanize/skill directory. # # This file is sourced by humanize.sh and depends on: # - monitor-common.sh (monitor_get_yaml_value, monitor_format_timestamp, etc.) # - humanize.sh (humanize_split_to_array) -# Monitor ask-codex skill invocations from .humanize/skill +# Monitor skill invocations from .humanize/skill # Shows a fixed status bar with aggregate stats and latest invocation details, # with live output display in the scrollable area below. +# +# Accepts --tool-filter to show only invocations from a +# specific tool. Without the filter, all invocations are shown. _humanize_monitor_skill() { # Enable 0-indexed arrays in zsh for bash compatibility # no_monitor suppresses background job notifications ([1] PID) @@ -23,11 +26,16 @@ _humanize_monitor_skill() { local check_interval=2 local status_bar_height=9 local once_mode=false + local tool_filter="" # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in --once) once_mode=true; shift ;; + --tool-filter) + tool_filter="${2:-}" + shift 2 + ;; *) shift ;; esac done @@ -35,10 +43,37 @@ _humanize_monitor_skill() { # Check if .humanize/skill exists if [[ ! -d "$skill_dir" ]]; then echo "Error: $skill_dir directory not found in current directory" - echo "Run /humanize:ask-codex first to create skill invocations" + echo "Run /humanize:ask-codex or /humanize:ask-gemini first to create skill invocations" return 1 fi + # Determine the tool for a given invocation directory. + # Reads metadata.md first (completed), falls back to input.md (running). + # Returns: codex, gemini, or unknown + _skill_get_tool() { + local dir="$1" + if [[ -f "$dir/metadata.md" ]]; then + local t=$(monitor_get_yaml_value "tool" "$dir/metadata.md") + [[ -n "$t" ]] && { echo "$t"; return; } + fi + if [[ -f "$dir/input.md" ]]; then + local t=$(grep -E '^- Tool:' "$dir/input.md" 2>/dev/null | sed 's/- Tool: //') + [[ -n "$t" ]] && { echo "$t"; return; } + fi + echo "unknown" + } + + # Check whether a directory passes the current tool filter. + # Returns 0 (pass) or 1 (skip). + _skill_passes_filter() { + [[ -z "$tool_filter" ]] && return 0 + local t=$(_skill_get_tool "$1") + [[ "$t" == "$tool_filter" ]] && return 0 + # Legacy invocations without a tool tag are treated as codex + [[ "$t" == "unknown" && "$tool_filter" == "codex" ]] && return 0 + return 1 + } + # List all valid skill invocation directories sorted newest-first # Skill dirs use YYYY-MM-DD_HH-MM-SS or YYYY-MM-DD_HH-MM-SS-PID-RANDOM naming _skill_list_dirs_sorted() { @@ -47,7 +82,9 @@ _humanize_monitor_skill() { [[ -z "$d" ]] && continue [[ ! -d "$d" ]] && continue local name=$(basename "$d") - [[ "$name" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2} ]] && dirs+=("$d") + [[ "$name" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2} ]] || continue + _skill_passes_filter "$d" || continue + dirs+=("$d") done < <(find "$skill_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null) printf '%s\n' "${dirs[@]}" | sort -r } @@ -88,6 +125,7 @@ _humanize_monitor_skill() { [[ ! -d "$d" ]] && continue local name=$(basename "$d") [[ ! "$name" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2} ]] && continue + _skill_passes_filter "$d" || continue ((total++)) if [[ -f "$d/metadata.md" ]]; then local st=$(monitor_get_yaml_value "status" "$d/metadata.md") @@ -127,6 +165,7 @@ _humanize_monitor_skill() { # Find the best file to monitor for a skill invocation # Searches both global cache (~/.cache/humanize/), local cache ($dir/cache/), # and project-local files (.humanize/skill/) for the best content. + # Supports both codex (codex-run.*) and gemini (gemini-run.*) cache files. _skill_find_monitored_file() { local dir="$1" local gcache=$(_skill_find_cache_dir "$dir") @@ -134,18 +173,29 @@ _humanize_monitor_skill() { local is_running=false [[ ! -f "$dir/metadata.md" ]] && is_running=true + # Determine which tool produced this invocation for cache file naming + local inv_tool=$(_skill_get_tool "$dir") + local run_prefix="codex-run" + [[ "$inv_tool" == "gemini" ]] && run_prefix="gemini-run" + # Helper: check a cache directory for best file # Args: cache_dir, prefer_log (true for running, false for completed) _check_cache_files() { local c="$1" prefer_log="$2" [[ ! -d "$c" ]] && return if [[ "$prefer_log" == "true" ]]; then + [[ -f "$c/${run_prefix}.log" && -s "$c/${run_prefix}.log" ]] && { echo "$c/${run_prefix}.log"; return; } + [[ -f "$c/${run_prefix}.out" && -s "$c/${run_prefix}.out" ]] && { echo "$c/${run_prefix}.out"; return; } + [[ -f "$c/${run_prefix}.log" ]] && { echo "$c/${run_prefix}.log"; return; } + # Fallback: try the other prefix for legacy/mixed invocations [[ -f "$c/codex-run.log" && -s "$c/codex-run.log" ]] && { echo "$c/codex-run.log"; return; } - [[ -f "$c/codex-run.out" && -s "$c/codex-run.out" ]] && { echo "$c/codex-run.out"; return; } - [[ -f "$c/codex-run.log" ]] && { echo "$c/codex-run.log"; return; } + [[ -f "$c/gemini-run.log" && -s "$c/gemini-run.log" ]] && { echo "$c/gemini-run.log"; return; } else + [[ -f "$c/${run_prefix}.out" && -s "$c/${run_prefix}.out" ]] && { echo "$c/${run_prefix}.out"; return; } + [[ -f "$c/${run_prefix}.log" && -s "$c/${run_prefix}.log" ]] && { echo "$c/${run_prefix}.log"; return; } + # Fallback [[ -f "$c/codex-run.out" && -s "$c/codex-run.out" ]] && { echo "$c/codex-run.out"; return; } - [[ -f "$c/codex-run.log" && -s "$c/codex-run.log" ]] && { echo "$c/codex-run.log"; return; } + [[ -f "$c/gemini-run.out" && -s "$c/gemini-run.out" ]] && { echo "$c/gemini-run.out"; return; } fi } @@ -166,6 +216,15 @@ _humanize_monitor_skill() { echo "" } + # Build the monitor title based on filter + _skill_monitor_title() { + case "$tool_filter" in + codex) echo " Humanize Skill Monitor [codex]" ;; + gemini) echo " Humanize Skill Monitor [gemini]" ;; + *) echo " Humanize Skill Monitor" ;; + esac + } + # Draw the status bar at the top _skill_draw_status_bar() { local latest_dir="$1" @@ -186,17 +245,21 @@ _humanize_monitor_skill() { # Parse latest invocation metadata local inv_status="running" model="N/A" effort="N/A" duration="N/A" started_at="N/A" + local inv_tool="unknown" if [[ -n "$latest_dir" && -f "$latest_dir/metadata.md" ]]; then inv_status=$(monitor_get_yaml_value "status" "$latest_dir/metadata.md") model=$(monitor_get_yaml_value "model" "$latest_dir/metadata.md") effort=$(monitor_get_yaml_value "effort" "$latest_dir/metadata.md") duration=$(monitor_get_yaml_value "duration" "$latest_dir/metadata.md") started_at=$(monitor_get_yaml_value "started_at" "$latest_dir/metadata.md") + inv_tool=$(monitor_get_yaml_value "tool" "$latest_dir/metadata.md") elif [[ -n "$latest_dir" && -f "$latest_dir/input.md" ]]; then model=$(grep -E '^- Model:' "$latest_dir/input.md" 2>/dev/null | sed 's/- Model: //') effort=$(grep -E '^- Effort:' "$latest_dir/input.md" 2>/dev/null | sed 's/- Effort: //') + inv_tool=$(grep -E '^- Tool:' "$latest_dir/input.md" 2>/dev/null | sed 's/- Tool: //') fi inv_status="${inv_status:-unknown}"; model="${model:-N/A}"; effort="${effort:-N/A}" + inv_tool="${inv_tool:-unknown}" # Status color local status_color="$dim" @@ -235,11 +298,19 @@ _humanize_monitor_skill() { cache_display="...${cache_display: -$csuffix_len}" fi + # Model display: for gemini, no effort; for codex, show (effort) + local model_display="$model" + if [[ "$inv_tool" == "gemini" ]] || [[ "$effort" == "N/A" ]]; then + model_display="$model" + else + model_display="$model ($effort)" + fi + tput sc tput cup 0 0 # Line 1: Title - printf "${bg}${bold}%-${term_width}s${reset}${clr_eol}\n" " Humanize Skill Monitor" + printf "${bg}${bold}%-${term_width}s${reset}${clr_eol}\n" "$(_skill_monitor_title)" # Line 2: Aggregate stats printf "${cyan}Total:${reset} ${bold}${total}${reset} invocations" [[ "$success" -gt 0 ]] && printf " | ${green}${success} success${reset}" @@ -248,8 +319,8 @@ _humanize_monitor_skill() { [[ "$empty" -gt 0 ]] && printf " | ${yellow}${empty} empty${reset}" [[ "$running" -gt 0 ]] && printf " | ${yellow}${running} running${reset}" printf "${clr_eol}\n" - # Line 3: Focused invocation status + model + duration - printf "${magenta}Focused:${reset} ${status_color}%s${reset} | ${yellow}Model:${reset} %s (%s) | ${cyan}Duration:${reset} %s${clr_eol}\n" "$inv_status" "$model" "$effort" "${duration:-N/A}" + # Line 3: Focused invocation status + tool + model + duration + printf "${magenta}Focused:${reset} ${status_color}%s${reset} | ${dim}[%s]${reset} ${yellow}Model:${reset} %s | ${cyan}Duration:${reset} %s${clr_eol}\n" "$inv_status" "$inv_tool" "$model_display" "${duration:-N/A}" # Line 4: Started at printf "${cyan}Started:${reset} %s${clr_eol}\n" "$start_display" # Line 5: Question @@ -269,7 +340,9 @@ _humanize_monitor_skill() { if [[ "$once_mode" == "true" ]]; then local latest=$(_skill_find_latest_dir) if [[ -z "$latest" ]]; then - echo "No skill invocations found in $skill_dir" + local filter_msg="" + [[ -n "$tool_filter" ]] && filter_msg=" (filter: $tool_filter)" + echo "No skill invocations found in $skill_dir$filter_msg" return 1 fi @@ -283,24 +356,29 @@ _humanize_monitor_skill() { local -a stats humanize_split_to_array stats "$(_skill_count_stats)" local inv_status="running" model="N/A" effort="N/A" duration="N/A" started_at="N/A" + local inv_tool="unknown" if [[ -f "$focus_dir/metadata.md" ]]; then inv_status=$(monitor_get_yaml_value "status" "$focus_dir/metadata.md") model=$(monitor_get_yaml_value "model" "$focus_dir/metadata.md") effort=$(monitor_get_yaml_value "effort" "$focus_dir/metadata.md") duration=$(monitor_get_yaml_value "duration" "$focus_dir/metadata.md") started_at=$(monitor_get_yaml_value "started_at" "$focus_dir/metadata.md") + inv_tool=$(monitor_get_yaml_value "tool" "$focus_dir/metadata.md") fi + inv_tool="${inv_tool:-unknown}" local question=$(_skill_get_question "$focus_dir") local cache_dir=$(_skill_find_cache_dir "$focus_dir") + local title=$(_skill_monitor_title) echo "==========================================" - echo " Humanize Skill Monitor" + echo "$title" echo "==========================================" echo "" echo "Total Invocations: ${stats[0]}" echo " Success: ${stats[1]} Error: ${stats[2]} Timeout: ${stats[3]} Empty: ${stats[4]} Running: ${stats[5]}" echo "" echo "Focused: $(basename "$focus_dir")" + echo " Tool: ${inv_tool}" echo " Status: ${inv_status:-unknown}" echo " Model: ${model:-N/A} (${effort:-N/A})" echo " Duration: ${duration:-N/A}" @@ -329,14 +407,16 @@ _humanize_monitor_skill() { while IFS= read -r d; do [[ -z "$d" ]] && continue local name=$(basename "$d") - local st="running" dur="" + local st="running" dur="" t="?" if [[ -f "$d/metadata.md" ]]; then st=$(monitor_get_yaml_value "status" "$d/metadata.md") dur=$(monitor_get_yaml_value "duration" "$d/metadata.md") + t=$(monitor_get_yaml_value "tool" "$d/metadata.md") fi + t="${t:-?}" local q=$(_skill_get_question "$d") [[ ${#q} -gt 50 ]] && q="${q:0:47}..." - printf " %-38s %-14s %-6s %s\n" "$name" "$st" "$dur" "$q" + printf " %-38s %-7s %-14s %-6s %s\n" "$name" "[$t]" "$st" "$dur" "$q" ((count++)) [[ $count -ge 10 ]] && break done < <(_skill_list_dirs_sorted) diff --git a/skills/ask-gemini/SKILL.md b/skills/ask-gemini/SKILL.md new file mode 100644 index 00000000..e31cdd57 --- /dev/null +++ b/skills/ask-gemini/SKILL.md @@ -0,0 +1,61 @@ +--- +name: ask-gemini +description: Consult Gemini as an independent expert with deep web research. Sends a question or task to Gemini CLI and returns a research-backed response. +argument-hint: "[--gemini-model MODEL] [--gemini-timeout SECONDS] [question or task]" +allowed-tools: "Bash(${CLAUDE_PLUGIN_ROOT}/scripts/ask-gemini.sh:*)" +--- + +# Ask Gemini + +Send a question or task to Gemini and return a research-backed response. +Gemini is always instructed to perform web research via Google Search, +making this ideal for deep-research tasks that benefit from up-to-date +internet information. + +## How to Use + +Do not pass free-form user text to the shell unquoted. The question or task may contain spaces or shell metacharacters such as `(`, `)`, `;`, `#`, `*`, or `[`. + +If the user only supplied a question or task, execute: + +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/ask-gemini.sh" "$ARGUMENTS" +``` + +If the user supplied flags such as `--gemini-model` or `--gemini-timeout`, reconstruct the command so those flags remain separate shell arguments and the remaining free-form question is passed as one quoted final argument. + +Example: + +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/ask-gemini.sh" --gemini-model gemini-2.5-pro "What are the latest Rust async runtime benchmarks?" +``` + +Never run this unsafe form: + +```bash +"${CLAUDE_PLUGIN_ROOT}/scripts/ask-gemini.sh" $ARGUMENTS +``` + +because the shell will re-parse the question text and can fail before `ask-gemini.sh` starts. + +## Interpreting Output + +- The script outputs Gemini's response to **stdout** and status info to **stderr** +- Read the stdout output carefully and incorporate Gemini's response into your answer +- Gemini's responses are research-backed with web sources; relay source citations when available +- If the script exits with a non-zero code, report the error to the user + +## Error Handling + +| Exit Code | Meaning | +|-----------|---------| +| 0 | Success - Gemini response is in stdout | +| 1 | Validation error (missing gemini, empty question, invalid flags) | +| 124 | Timeout - suggest using `--gemini-timeout` with a larger value | +| Other | Gemini process error - report the exit code and any stderr output | + +## Notes + +- The response is saved to `.humanize/skill//output.md` for reference +- Default model is `gemini-3.1-pro-preview` with a 3600-second timeout +- Gemini is always instructed to perform Google Search for up-to-date information