Experimental note creation with claude added. Requires claude-code try something like notestorm -X 'detailed instructions for setting up a reverse ssh tunnel'

This commit is contained in:
Storm Dragon
2025-07-23 17:18:03 -04:00
parent c4854b5150
commit cbe830098c

345
notestorm
View File

@@ -32,6 +32,55 @@ unset modified
# Functions section
check_dependencies() {
# Check for required dependencies
local missing_deps=()
# Critical dependencies
if ! command -v dialog &> /dev/null; then
missing_deps+=("dialog")
fi
if ! command -v markdown &> /dev/null; then
missing_deps+=("markdown (discount package)")
fi
# Check for at least one pager
if ! command -v w3m &> /dev/null && \
! command -v elinks &> /dev/null && \
! command -v lynx &> /dev/null; then
missing_deps+=("w3m, elinks, or lynx")
fi
# Optional but recommended
if ! command -v gpg &> /dev/null; then
echo "Warning: GPG not found. Note encryption will not be available." >&2
fi
if ! command -v git &> /dev/null; then
echo "Warning: Git not found. Git backup functionality will not be available." >&2
fi
if ! command -v zip &> /dev/null; then
echo "Warning: Zip not found. Zip backup functionality will not be available." >&2
fi
if ! command -v unzip &> /dev/null; then
echo "Warning: Unzip not found. Zip restore functionality will not be available." >&2
fi
# Report missing critical dependencies
if [[ ${#missing_deps[@]} -gt 0 ]]; then
echo "Error: Missing required dependencies:" >&2
for dep in "${missing_deps[@]}"; do
echo " - $dep" >&2
done
echo >&2
echo "Please install the missing dependencies and try again." >&2
exit 1
fi
}
cleanup() {
if [[ ${#modified} -lt 1 ]]; then
exit 0
@@ -139,12 +188,36 @@ add_note() {
decrypt_note() {
#returns the note to its unencrypted state.
if gpg -o "${1%.gpg}" -d "$1" ; then
local output_file="${1%.gpg}"
# Check if output file already exists
if [[ -f "$output_file" ]]; then
if [[ "$(yesno "$(gettext "File already exists. Overwrite?")")" != "Yes" ]]; then
return
fi
fi
# Attempt decryption with better error handling
local gpg_error
if gpg_error=$(gpg -o "$output_file" -d "$1" 2>&1); then
rm -f "$1"
infobox "$(gettext "Note decrypted and saved as: ") \"${1%.gpg}\""
infobox "$(gettext "Note decrypted and saved as: ") \"$output_file\""
modified="note decrypted"
return
fi
infobox "$(gettext "Decryption failed.")"
# Provide more specific error messages
case "$gpg_error" in
*"Bad passphrase"*|*"bad passphrase"*)
infobox "$(gettext "Decryption failed: Incorrect passphrase.")"
;;
*"No such file"*)
infobox "$(gettext "Decryption failed: File not found.")"
;;
*)
infobox "$(gettext "Decryption failed: ") $gpg_error"
;;
esac
}
delete_note() {
@@ -178,9 +251,16 @@ display_note() {
exit 0
fi
if [[ "${1##*.}" == "gpg" ]]; then
gpg -d "$1" | markdown | eval "$pager"
# Handle GPG decryption errors gracefully
if ! gpg -d "$1" 2>/dev/null | markdown | "${pager[@]}"; then
infobox "$(gettext "Failed to decrypt or display note. Check your passphrase and try again.")"
return 1
fi
else
markdown "$1" | eval "$pager"
if ! markdown "$1" | "${pager[@]}"; then
infobox "$(gettext "Failed to display note. Check if file exists and markdown is installed.")"
return 1
fi
fi
}
@@ -201,20 +281,42 @@ encrypt_note() {
if [[ $# -ne 1 ]]; then
return
fi
# Validate input file exists
if [[ ! -f "$1" ]]; then
infobox "$(gettext "Error: Source file does not exist.")"
return
fi
# Get a human readable name for the new note.
local noteName="$(inputbox "$(gettext "Please enter a descriptive name for the encrypted note:")")"
if [[ -z "$noteName" ]]; then
# No name supplied, so return
return
fi
# Sanitize filename - remove dangerous characters
noteName="${noteName//[^a-zA-Z0-9._-]/_}"
local output_file="${1%/*}/${noteName%.md*}.md.gpg"
# Check if output file already exists
if [[ -f "$output_file" ]]; then
if [[ "$(yesno "$(gettext "Encrypted file already exists. Overwrite?")")" != "Yes" ]]; then
return
fi
fi
# Encrypt the given file and if successful, remove the original.
if gpg -o "${1%/*}/${noteName%.md*}.md.gpg" -c "$1" ; then
local gpg_error
if gpg_error=$(gpg -o "$output_file" -c "$1" 2>&1); then
rm -f "$1"
infobox "$(gettext "Note encrypted.")"
infobox "$(gettext "Note encrypted as: ") \"$output_file\""
modified="encrypted note added"
return
fi
infobox "$(gettext "Encryption failed.")"
# Provide specific error message
infobox "$(gettext "Encryption failed: ") $gpg_error"
}
list_notes() {
@@ -234,11 +336,27 @@ initialize_git() {
fi
message="$(gettext "Please enter the url to your git repository for notes.")"
local gitURL="$(inputbox "$message")"
# Enhanced URL validation
if [[ ${#gitURL} -lt 3 ]]; then
message="$(gettext "Invalid URL detected, exiting.")"
infobox "$message"
exit 1
fi
# Check for valid git URL patterns and sanitize
if [[ ! "$gitURL" =~ ^(https?://|git@|ssh://|file://|/) ]]; then
message="$(gettext "Invalid git URL format. Must start with https://, git@, ssh://, file://, or / for local paths.")"
infobox "$message"
exit 1
fi
# Prevent command injection in URL
if [[ "$gitURL" == *";"* ]] || [[ "$gitURL" == *'`'* ]] || [[ "$gitURL" == *'$'* ]] || [[ "$gitURL" == *'('* ]] || [[ "$gitURL" == *')'* ]]; then
message="$(gettext "Invalid characters detected in URL.")"
infobox "$message"
exit 1
fi
{ git -C "$xdgPath/notestorm/notes" init
git -C "$xdgPath/notestorm/notes" remote add origin "$gitURL"
git -C "$xdgPath/notestorm/notes" add -A
@@ -262,29 +380,204 @@ original_note() {
fi
}
backup_notes() {
# Create a zip backup of all notes with optional password
# Usage: backup_notes [password]
local password="$1"
local backup_file="$HOME/Documents/notestorm-$(date +%Y-%m-%d).zip"
# Check if zip command exists
if ! command -v zip &> /dev/null; then
infobox "$(gettext "Error: zip command not found. Please install zip package.")"
return 1
fi
# Create Documents directory if it doesn't exist
mkdir -p "$HOME/Documents"
# Check if backup file already exists
if [[ -f "$backup_file" ]]; then
if [[ "$(yesno "$(gettext "Backup file already exists. Overwrite?")")" != "Yes" ]]; then
return 0
fi
fi
# Create backup with or without password
local zip_cmd="zip -r"
if [[ -n "$password" ]]; then
zip_cmd="$zip_cmd -P \"$password\""
fi
# Change to parent directory to avoid including full path in zip
local current_dir="$(pwd)"
cd "$xdgPath"
if [[ -n "$password" ]]; then
zip -r -P "$password" "$backup_file" "notestorm/notes" 2>/dev/null
else
zip -r "$backup_file" "notestorm/notes" 2>/dev/null
fi
local zip_result=$?
cd "$current_dir"
if [[ $zip_result -eq 0 ]]; then
infobox "$(gettext "Backup created successfully:") $backup_file"
return 0
else
infobox "$(gettext "Backup failed.")"
return 1
fi
}
restore_notes() {
# Restore notes from a zip backup
# Usage: restore_notes [zip_file]
local zip_file="$1"
# Check if unzip command exists
if ! command -v unzip &> /dev/null; then
infobox "$(gettext "Error: unzip command not found. Please install unzip package.")"
return 1
fi
# If no file specified, let user choose
if [[ -z "$zip_file" ]]; then
zip_file="$(inputbox "$(gettext "Enter path to backup zip file:")" "$HOME/Documents/")"
if [[ -z "$zip_file" ]]; then
return 0
fi
fi
# Check if zip file exists
if [[ ! -f "$zip_file" ]]; then
infobox "$(gettext "Error: Backup file not found:") $zip_file"
return 1
fi
# Warn about overwriting existing notes
if [[ -d "$xdgPath/notestorm/notes" ]] && [[ -n "$(ls -A "$xdgPath/notestorm/notes" 2>/dev/null)" ]]; then
if [[ "$(yesno "$(gettext "This will overwrite existing notes. Continue?")")" != "Yes" ]]; then
return 0
fi
# Backup existing notes first
local backup_existing="$xdgPath/notestorm/notes-backup-$(date +%Y-%m-%d-%H%M%S)"
mv "$xdgPath/notestorm/notes" "$backup_existing"
infobox "$(gettext "Existing notes backed up to:") $backup_existing"
fi
# Create notes directory
mkdir -p "$xdgPath/notestorm"
# Extract the backup
if unzip -o "$zip_file" -d "$xdgPath" 2>/dev/null; then
infobox "$(gettext "Notes restored successfully from:") $zip_file"
return 0
else
infobox "$(gettext "Restore failed. Check if the file is a valid zip or if password is required.")"
return 1
fi
}
claude_note() {
# Create a note using Claude Code with the provided prompt
# Usage: claude_note "prompt text"
local prompt="$1"
# Check if prompt is provided
if [[ -z "$prompt" ]]; then
gettext "Error: No prompt provided."
echo
exit 1
fi
# Check if claude command is available
if ! command -v claude &> /dev/null; then
echo "$(gettext "Error: claude command not found. Please install Claude Code.")"
echo
echo "$(gettext "Installation instructions:")"
echo "$(gettext "- Arch Linux: Install from AUR with 'yay -S claude-code' or 'paru -S claude-code'")"
echo "$(gettext "- Debian/Ubuntu: Install Node.js then run 'npm install -g @anthropic-ai/claude-code'")"
echo "$(gettext "- Generic: See https://docs.anthropic.com/en/docs/claude-code/quickstart")"
echo
exit 1
fi
# Generate note name based on timestamp
local noteName=$(date '+%s')
while [ -f "${xdgPath}/notestorm/notes/${noteName}.md" ]; do
((noteName++))
done
# Enhance the prompt to ensure proper markdown formatting and direct content
local enhanced_prompt="Create a markdown-formatted note with the following content: ${prompt}
Please format your response as a proper markdown document that can be saved directly as a .md file. Start with a clear title using # header syntax. Provide only the note content itself, not meta-commentary about creating the note. The note should be similar in style to a CLI reference with clear sections and practical information."
# Call claude and capture output
local claude_output
if ! claude_output=$(claude -p "$enhanced_prompt" 2>&1); then
gettext "Error: Failed to get response from Claude Code: "
echo "$claude_output"
exit 1
fi
# Write the output to the note file
echo "$claude_output" > "${xdgPath}/notestorm/notes/${noteName}.md"
# Try to extract a meaningful name from the first line
local first_line="$(head -n 1 "${xdgPath}/notestorm/notes/${noteName}.md" | head -c 100)"
# Remove markdown headers and clean up the name
first_line="${first_line#\# }"
first_line="${first_line#\#\# }"
first_line="${first_line#\#\#\# }"
first_line="${first_line//[[:space:]]/_}"
first_line="${first_line//[^a-zA-Z0-9._-]/_}"
# Rename if we got a meaningful name and it doesn't conflict
if [[ -n "$first_line" && "$first_line" != "_" && ! -e "${xdgPath}/notestorm/notes/${first_line}.md" ]]; then
mv "${xdgPath}/notestorm/notes/${noteName}.md" "${xdgPath}/notestorm/notes/${first_line}.md"
noteName="${first_line}"
fi
modified="note added"
gettext "Claude note created: "
echo "${noteName}.md"
}
# Configuration section
# Available arguments in both long and short versions stored in associative array.
declare -A argList=(
[g]="git"
[l]="list"
[n]="new")
[n]="new"
[r]="restore"
[z]="zip"
[C]="claude")
# Make the args a continuous string.
short="${!argList[*]}"
short="${short// /}"
long="${argList[*]}"
long="${long// /}"
# Build short options manually to handle optional arguments
short="glnr:z:C:"
long="git,list,new,restore:,zip:,claude:"
editor="${EDITOR:-nano}"
messageTimeout=${messageTimeout:-1}
# Set pager
# Set pager - using arrays to prevent command injection
if command -v w3m &> /dev/null ; then
pager="w3m -T text/html"
pager=("w3m" "-T" "text/html")
elif command -v elinks &> /dev/null ; then
pager="elinks -force-html"
pager=("elinks" "-force-html")
elif command -v lynx &> /dev/null ; then
pager="lynx -force_html"
pager=("lynx" "-force_html")
else
pager="${PAGER:-more}"
# For PAGER, we need to handle it carefully since it could be a complex command
if [[ -n "${PAGER}" ]]; then
# Split PAGER on whitespace - this is a compromise for compatibility
read -ra pager <<< "${PAGER}"
else
pager=("more")
fi
fi
xdgPath="${XDG_CONFIG_HOME:-$HOME/.config}"
# set up the notes directory
@@ -308,6 +601,9 @@ fi
# Code section
# Check dependencies before proceeding
check_dependencies
# If the only arg is a number, display that note, then exit.
if [[ "$*" =~ [0-9]+ ]]; then
display_note $*
@@ -316,13 +612,17 @@ fi
# Parse non-numeric command line args.
if ! options=$(getopt -o "$short" -l "$long" -n "notestorm" -- "$@"); then
gettext -e "Usage: notestorm launch interactive session.\n"
gettext -e -- "-C or --claude \"prompt\" create a note using Claude Code with the provided prompt.\n"
gettext -e -- "-g or --git set up backups to git. Requires existing git repository with branch name \"master\".\n"
gettext -e -- "-n or --new add a new note without opening an interactive session.\n"
gettext -e -- "-r or --restore [file] restore notes from a zip backup file.\n"
gettext -e -- "-z or --zip password create a zip backup of all notes with password. Use empty string \"\" for no password.\n"
echo
gettext -e "You can use markdown syntax in notes.\n"
gettext -e "Notes are named numerically. they can be renamed and will still show up so long as they end with a .md extension.\n"
gettext -e "To get a numbered list, instead of the interactive menu, use the -l option.\n"
gettext -e "To show a note without using the interactive interface and pager, give the notes number as the only argument.\n"
gettext -e "Zip backups are saved to ~/Documents/notestorm-YYYY-MM-DD.zip\n"
gettext "Notes are saved in "
echo "${xdgPath}/notestorm/notes"
exit 1
@@ -335,6 +635,15 @@ while [ $# -gt 0 ]; do
"-g"|"--git") initialize_git;;
"-l"|"--list") list_notes;;
"-n"|"--new") add_note;;
"-r"|"--restore")
shift
restore_notes "$1";;
"-z"|"--zip")
shift
backup_notes "$1";;
"-C"|"--claude")
shift
claude_note "$1";;
esac
shift
done