323 lines
10 KiB
Bash
Executable file
323 lines
10 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# opsearch.sh - A tool to search through 1Password items
|
|
#
|
|
# Usage:
|
|
# opsearch <search_term> [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 <search_term> [options]
|
|
|
|
Options:
|
|
-c, --category <category> Filter by category (login, password, document, etc.)
|
|
-t, --tag <tag> Filter by tag
|
|
-v, --vault <vault> Specify vault to search in
|
|
-f, --field <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"
|
|
|