#!/usr/bin/env bash # opsearch.sh - A tool to search through 1Password items # # Usage: # opsearch [options] # # Options: # -c, --category Filter by category (login, password, document, etc.) # -t, --tag Filter by tag # -v, --vault Specify vault to search in # -f, --field Search only in specific fields # -j, --json Output results in JSON format # -d, --detail Show detailed output for each item # -n, --nushell Use Nushell for output formatting (if available) # -p, --password Retrieve and copy password after selection # -h, --help Show this help message set -e # Default options FORMAT="json" SHOW_DETAIL=false CATEGORY="" TAG="" VAULT="" FIELD="" SEARCH_TERM="" USE_NUSHELL=false GET_PASSWORD=false # Check if 1Password CLI is available if ! command -v op &>/dev/null; then echo "Error: 1Password CLI (op) not found." echo "Please install it from https://1password.com/downloads/command-line/" exit 1 fi # Check if 1Password CLI is signed in if ! op account get --format=json &>/dev/null; then echo "You need to sign in to 1Password CLI first." echo "Run: eval \$(op signin)" exit 1 fi # Function to display help show_help() { cat << EOF opsearch - Search 1Password items with grep-like functionality Usage: opsearch [options] Options: -c, --category Filter by category (login, password, document, etc.) -t, --tag Filter by tag -v, --vault Specify vault to search in -f, --field Search only in specific fields -j, --json Output results in JSON format -d, --detail Show detailed output for each item -n, --nushell Use Nushell for output formatting (if available) -p, --password Retrieve and copy password after selection -h, --help Show this help message Examples: opsearch github # Search for "github" in all items opsearch amazon -c login # Search for "amazon" in login items opsearch bank -v Personal # Search for "bank" in the Personal vault opsearch -t finance # Show all items with "finance" tag opsearch email -f username # Search for "email" in username fields opsearch ssh -d # Show detailed info for SSH items opsearch github -p # Get GitHub password after selecting item EOF exit 0 } # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in -h|--help) show_help ;; -j|--json) FORMAT="json" shift ;; -d|--detail) SHOW_DETAIL=true shift ;; -n|--nushell) USE_NUSHELL=true shift ;; -p|--password) GET_PASSWORD=true shift ;; -c|--category) CATEGORY="$2" shift 2 ;; -t|--tag) TAG="$2" shift 2 ;; -v|--vault) VAULT="$2" shift 2 ;; -f|--field) FIELD="$2" shift 2 ;; -*) echo "Unknown option: $1" show_help ;; *) if [[ -z "$SEARCH_TERM" ]]; then SEARCH_TERM="$1" else echo "Error: Multiple search terms provided. Use quotes for terms with spaces." exit 1 fi shift ;; esac done # Check for required tools check_dependencies() { if ! command -v jq &> /dev/null; then echo "Error: jq is required for this script to function properly." echo "Please install it using your package manager." exit 1 fi if [[ "$USE_NUSHELL" == "true" ]] && ! command -v nu &> /dev/null; then echo "Warning: Nushell not found, falling back to jq for formatting." USE_NUSHELL=false fi if [[ "$GET_PASSWORD" == "true" ]]; then if ! command -v xclip &> /dev/null && ! command -v pbcopy &> /dev/null && ! command -v wl-copy &> /dev/null; then echo "Warning: No clipboard utility found (xclip, pbcopy, or wl-copy required)." echo "Password will be displayed but not copied." fi fi } # Build the 1Password CLI command OP_CMD="op item list --format=json" # Add filters to the command if [[ -n "$CATEGORY" ]]; then OP_CMD="$OP_CMD --categories=$CATEGORY" fi if [[ -n "$TAG" ]]; then OP_CMD="$OP_CMD --tags=$TAG" fi if [[ -n "$VAULT" ]]; then OP_CMD="$OP_CMD --vault=$VAULT" fi # Function to colorize the output highlight_match() { local text="$1" local pattern="$2" echo "$text" | grep --color=always -i "$pattern" || echo "$text" } # Function to copy to clipboard copy_to_clipboard() { local text="$1" if command -v wl-copy &> /dev/null; then echo -n "$text" | wl-copy echo "Copied to clipboard with wl-copy" elif command -v xclip &> /dev/null; then echo -n "$text" | xclip -selection clipboard echo "Copied to clipboard with xclip" elif command -v pbcopy &> /dev/null; then echo -n "$text" | pbcopy echo "Copied to clipboard with pbcopy" else echo "No clipboard utility found. Here's the value:" echo "$text" fi } # Main function to format and display results display_items() { local items="$1" local count=$(echo "$items" | jq 'length') if [[ $count -eq 0 ]]; then echo "No items found matching your search criteria." exit 0 fi echo "Found $count matching items:" if [[ "$USE_NUSHELL" == "true" ]]; then # Use Nushell for pretty output echo "$items" | nu -c "open - | select id title category updated vault_id | sort-by title" else # Use jq for formatting echo "$items" | jq -r ' ["ID", "TITLE", "CATEGORY", "UPDATED"] as $headers | ([$headers] + (. | map([.id, .title, .category, .updated[0:10]]))) | .[] | @tsv' | column -t -s $'\t' fi if [[ $count -gt 0 ]]; then echo "" select_item "$items" fi } # Function to select an item and show details or get password select_item() { local items="$1" local count=$(echo "$items" | jq 'length') echo "Select an item by number (1-$count) or press Enter to exit:" select opt in $(echo "$items" | jq -r '.[].title'); do if [[ -z "$opt" ]]; then echo "Exiting." exit 0 fi local idx=$((REPLY-1)) if [[ $idx -ge 0 && $idx -lt $count ]]; then local item_id=$(echo "$items" | jq -r ".[$idx].id") local item_title=$(echo "$items" | jq -r ".[$idx].title") echo "Selected: $item_title (ID: $item_id)" if [[ "$GET_PASSWORD" == "true" ]]; then # Get password and copy to clipboard local password=$(op item get "$item_id" --fields password) if [[ -n "$password" ]]; then copy_to_clipboard "$password" else echo "No password field found for this item." # Show available fields echo "Available fields:" op item get "$item_id" --format=json | jq -r '.fields[] | select(.id != "notesPlain") | "\(.id): \(.label)"' echo "Get a specific field? Enter field ID or press Enter to skip:" read -r field_id if [[ -n "$field_id" ]]; then local field_value=$(op item get "$item_id" --fields "$field_id") if [[ -n "$field_value" ]]; then copy_to_clipboard "$field_value" else echo "Field not found or empty." fi fi fi elif [[ "$SHOW_DETAIL" == "true" ]]; then # Show detailed information echo "Detailed information:" echo "------------------------------------------------" if [[ -n "$FIELD" ]]; then # Show only the requested field op item get "$item_id" --fields "$FIELD" else # Show all fields if [[ "$USE_NUSHELL" == "true" ]]; then op item get "$item_id" --format=json | nu -c "open - | get fields | where id != 'notesPlain' | sort-by label" else op item get "$item_id" --format=json | jq -r '.fields[] | select(.id != "notesPlain") | "\(.label): \(.value)"' fi fi echo "------------------------------------------------" else # Show basic information op item get "$item_id" --format=json | jq -r '.fields[] | select(.id == "username" or .id == "password" or .id == "notesPlain") | "\(.label): \(.value // "[Hidden]")"' fi echo "" echo "Select another item or press Ctrl+C to exit:" else echo "Invalid selection. Please choose a number between 1 and $count." fi done } # Execute search check_dependencies if [[ -z "$SEARCH_TERM" && -z "$TAG" && -z "$CATEGORY" ]]; then echo "Error: No search term provided." show_help exit 1 fi # Execute the command and store the results RESULT=$(eval "$OP_CMD") # Filter results based on search term if [[ -n "$SEARCH_TERM" ]]; then FILTERED_RESULT=$(echo "$RESULT" | jq --arg term "$SEARCH_TERM" -r '[.[] | select( (.title | ascii_downcase | contains($term | ascii_downcase)) or (.id | ascii_downcase | contains($term | ascii_downcase)) or (.additional_information | ascii_downcase | contains($term | ascii_downcase)) or (.urls != null and (.urls[] | ascii_downcase | contains($term | ascii_downcase))) or (.tags != null and (.tags[] | ascii_downcase | contains($term | ascii_downcase))) )]') else FILTERED_RESULT="$RESULT" fi # Display the results display_items "$FILTERED_RESULT"