migration

This commit is contained in:
2026-05-24 23:01:14 -07:00
parent 9efcc26da0
commit 7dc91ca752
16 changed files with 27124 additions and 1 deletions
+211 -1
View File
@@ -1,3 +1,213 @@
# himitsu # himitsu
himitsu - secrets generation using custom word-lists, character sets, and templating Memorable secret generator with configurable word lists and templates.
## Install
```bash
# Requires GNU coreutils (macOS: brew install coreutils)
git clone <repo> && cd himitsu
./install.sh
```
## Usage
### Generating secrets
```bash
himi # generate → clipboard
himi -o # generate → clipboard + stdout
himi -o secure # use 'secure' template → clipboard + stdout
himi -n 5 -o easy # generate 5 'easy' secrets → clipboard + stdout
himi test '{word}-{num:2}' # test a custom template
```
### Use in scripts
```bash
# SSH key with generated passphrase (on clipboard too, ready to save)
ssh-keygen -t ed25519 -f mykey -P "$(himi -o)"
# Encrypt a file
openssl enc -aes-256-cbc -pbkdf2 -in secrets.tar -out secrets.enc -k "$(himi -o secure)"
# Generate and reuse in the same session
PASS="$(himi -o)"
htpasswd -b -c .htpasswd admin "$PASS"
# Wi-Fi password for a new AP
nmcli dev wifi hotspot ssid mynet password "$(himi -o wifi)"
```
### Managing word lists and config
```bash
himi list # show lists and enabled status
himi add pokemon.txt # add + enable a list
himi disable pokemon # disable a list
himi enable pokemon # re-enable
himi rebuild # regenerate pool after manual changes
himi config # open config in $EDITOR (auto-rebuilds on change)
```
## Checking entropy
```bash
himi entropy # show all templates with entropy
himi entropy secure # show specific template
```
```
TEMPLATE EXAMPLE ENTROPY
-------- ------- -------
default MAPLE42-river17-K7M93 ≈57.9 bits
easy crane-B4R-842 ≈38.8 bits
secure helm-river-A3K9P-fox-M2X8R! ≈94.4 bits
pin 483927 ≈19.9 bits
diceware crane-helm-tiger-river-maple ≈69.0 bits
wifi TIGER-maple4827 ≈42.9 bits
phone ACFG-KM3Y-XTWD ≈55.0 bits
```
## Templates
Edit `~/.config/himi/config.sh` to customize:
```bash
TEMPLATES=(
"default|{word!}{num:2}-{word!}{num:2}-{alpha:3}{num:2}"
"easy|{word}-{alpha:3}-{num:3}"
"secure|{word}-{word}-{alpha:5}-{word}-{alpha:5}{special:1}"
"pin|{num:6}"
"diceware|{word}-{word}-{word}-{word}-{word}"
"wifi|{word!}-{word!}{num:4}"
"phone|{safe:4}-{safe:4}-{safe:4}"
)
```
**Tokens:**
| Token | Output | Entropy |
|-------|--------|---------|
| `{word}` | `maple` | ~13.8 bits* |
| `{WORD}` | `MAPLE` | ~13.8 bits* |
| `{Word}` | `Maple` | ~13.8 bits* |
| `{word!}` | `maple` or `MAPLE` | ~14.8 bits* |
| `{Word!}` | `maple`/`Maple`/`MAPLE` | ~15.4 bits* |
| `{alpha:N}` | N chars, Crockford Base32 | ~5.0 bits/char |
| `{safe:N}` | N chars, ultra-safe (no ambiguous chars) | ~4.6 bits/char |
| `{num:N}` | N digits | ~3.3 bits/char |
| `{special:N}` | N from `!?@#%~=+` | ~3.0 bits/char |
\* Word entropy depends on pool size. Values shown assume all built-in lists enabled (~14k words, ~13.8 bits/word).
## Entropy rules of thumb
| Bits | Strength | Use case |
|------|----------|----------|
| ~40 | Casual | Low-value accounts, rate-limited logins |
| ~50 | Reasonable | Personal accounts |
| ~60 | Strong | Important accounts, recommended minimum |
| ~80 | Very strong | High-value targets, offline cracking resistance |
| ~128 | Paranoid | Nation-state threat models |
Online attacks (rate-limited) need far less entropy than offline attacks (cracking leaked hashes). When in doubt, aim for 60+ bits.
## Uninstall
```bash
himi uninstall
```
## Included Wordlists
Word lists are curled from GitHub and cleaned up. Here's how each built-in list was made, you can use these as examples in case you want to add your own. The built-in wordlists provide 15175 total words, after deduplication.
### bips.txt (2048 words)
```bash
curl 'https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt' | \
tr -d '" ,[]\t' | tr 'A-Z' 'a-z' |
sed '/^$/d' | sort -u > bips.txt
```
### eff.txt (7776 words)
```bash
curl 'https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt | cut -f2' | \
tr -d '" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u > eff.txt
```
### female_names.txt (4775 words)
```bash
curl 'https://raw.githubusercontent.com/arineng/arincli/refs/heads/master/lib/female-first-names.txt' \
| tr -d '\" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u > ftemp
curl 'https://raw.githubusercontent.com/attackdebris/kerberos_enum_userlists/refs/heads/master/Female_First_Names_Top_500.txt' \
| tr -d '" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u >> ftemp
curl 'https://raw.githubusercontent.com/righteousgambit/quiet-riot/refs/heads/main/wordlists/femalenames-usa-top1000.txt' \
| tr -d '" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u >> ftemp
cat ftemp | sort -u > female_names.txt
```
### male_names.txt (1403 words)
```bash
curl 'https://raw.githubusercontent.com/arineng/arincli/refs/heads/master/lib/male-first-names.txt' \
| tr -d '" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u > mtemp
curl 'https://raw.githubusercontent.com/attackdebris/kerberos_enum_userlists/refs/heads/master/Male_First_Names_Top_500.txt' \
| tr -d '" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u >> mtemp
curl 'https://raw.githubusercontent.com/righteousgambit/quiet-riot/refs/heads/main/wordlists/malenames-usa-top1000.txt' \
| tr -d '" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u >> mtemp
cat mtemp | sort -u > male_names.txt
```
### pokemon.txt (1024 words)
```bash
curl 'https://raw.githubusercontent.com/sindresorhus/pokemon/main/data/en.json' \
| tr -d '" ,[]\t' | tr 'A-Z' 'a-z' \
| sed '/^$/d' | sort -u > pokemon.txt
```
### greek_gods.txt (445 words)
```bash
curl 'https://raw.githubusercontent.com/katkaypettitt/greek-gods/refs/heads/main/greek_gods.csv' \
| cut -d ',' -f1 | tr -d ' [],\"' |tr 'A-Z' 'a-z' | sort -u > greek_gods.txt
```
### colors.txt (5611 words)
```bash
curl -Ls 'https://raw.githubusercontent.com/meodai/color-names/refs/heads/main/src/colornames.csv' | \
awk -F',' 'NR > 1 {print $1}' | grep -E "^[A-Za-z]{4,12}$" | tr 'A-Z' 'a-z' \
>> colors.tmp
curl -Ls 'https://raw.githubusercontent.com/k-kawakami/colorfulnet/refs/heads/master/example_data/wikipedia-list-of-colors.txt' | \
tr 'A-Z ' 'a-z\n' | tr -d '()' | grep -E "^[a-z]{4,12}$" | sort -u >> colors.tmp
cat colors.tmp | sort -u | tee colors.txt | wc -l
```
### food.txt (1307 words)
```bash
curl -Ls 'https://raw.githubusercontent.com/schollz/food-identicon/master/ingredients.txt' | \
grep -Ev "(\ |:|â|¼|ã|\.)" | tr 'A-Z' 'a-z' | \
grep -E "^[a-z-]{4,12}$" >> food.tmp
curl -Ls 'https://gist.githubusercontent.com/peterdemin/920ec3eaaa0a9f3cafd3a855557f5e0c/raw/9c7337d7f6274de704f9018ed363d51dd7a0b128/food.txt' | \
tr ' -' '\n' | tr 'A-Z' 'a-z' | \
grep -E "^[a-z]{4,12}$" | sort -u >> food.tmp
cat food.tmp | sort -u | tee food.txt | wc -l
```
### animals.txt (1109 words)
```bash
curl -Ls 'https://raw.githubusercontent.com/skjorrface/animals.txt/refs/heads/master/animals.txt' | \
tr ' ' '\n' | tr 'A-Z' 'a-z' | grep -E "^[a-z-]{4,12}$" | \
sort -u >> animals.tmp
curl -Ls 'https://raw.githubusercontent.com/timgates42/animal-list-factory/refs/heads/master/animals.txt' | \
tr 'A-Z' 'a-z' | grep -E "^[a-z-]{4,12}$" | \
sort -u >> animals.tmp
curl -Ls 'https://raw.githubusercontent.com/juliandefreitas/entity_lists/refs/heads/master/animals.csv' | \
tr 'A-Z' 'a-z' | grep -E "^[a-z-]{4,12}$" | \
sort -u >> animals.tmp
curl -Ls 'https://raw.githubusercontent.com/MikeInnes/SourceWalk.jl/refs/heads/master/animals.txt' | \
tr 'A-Z' 'a-z' | grep -E "^[a-z-]{4,12}$" | \
sort -u >> animals.tmp
cat animals.tmp | sort -u | tee animals.txt | wc -l
```
+86
View File
@@ -0,0 +1,86 @@
#compdef himi
# himi zsh completion
# Drop in ~/.zfunc/ then run: compinit
_himi() {
local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/himi"
local lists_dir="${config_dir}/lists"
local pool_dir="${config_dir}/pool"
# Get template names from config
local -a templates
if [[ -f "${config_dir}/config.sh" ]]; then
templates=(${(f)"$(grep -E '^\s*"[a-z]+\|' "${config_dir}/config.sh" 2>/dev/null | sed 's/.*"\([a-z]*\)|.*/\1/')"})
fi
[[ ${#templates} -eq 0 ]] && templates=(default easy secure pin diceware)
# Get list names
local -a all_lists enabled_lists disabled_lists
disabled_lists=(all)
enabled_lists=(all)
if [[ -d "$lists_dir" ]]; then
for f in "$lists_dir"/*.txt(N); do
local name=${${f:t}%.txt}
all_lists+=($name)
if [[ -L "$pool_dir/$name.txt" ]]; then
enabled_lists+=($name)
else
disabled_lists+=($name)
fi
done
fi
local -a commands
commands=(
'add:Add and enable a word list'
'enable:Enable word list(s), or all'
'disable:Disable word list(s), or all'
'remove:Delete a word list permanently'
'rebuild:Rebuild pool from enabled lists'
'list:Show all lists and status'
'entropy:Show entropy for templates'
'test:Test a custom pattern'
'config:Edit config in $EDITOR'
'uninstall:Remove himi and all data'
)
_arguments -C \
'1:command:->cmd' \
'*:argument:->args' \
'-o[Output to stdout]' \
'-n[Number of secrets]:count:(1 3 5 10)' \
'-h[Show help]' \
'--help[Show help]'
case "$state" in
cmd)
_describe -t commands 'commands' commands
_describe -t templates 'templates' templates
;;
args)
case "${words[2]}" in
add)
_files -g '*.txt'
;;
enable)
_describe -t lists 'available lists' disabled_lists
;;
disable)
_describe -t lists 'enabled lists' enabled_lists
;;
remove)
_describe -t lists 'available lists' all_lists
;;
entropy)
_describe -t templates 'templates' templates
;;
test)
_message 'pattern (e.g. {word}-{num:3})'
;;
esac
;;
esac
}
_himi "$@"
+48
View File
@@ -0,0 +1,48 @@
# secret config — source'd by the secret command
# Location: ~/.config/himi/config.sh
# ------------------------------------------------------------------------------
# Character sets for random portions
# ------------------------------------------------------------------------------
# Crockford Base32 (32 chars, 5.0 bits/char)
ALPHA_CHARSET='0123456789ABCDEFGHJKMNPQRSTVWXYZ'
# Ultra-safe - no easily confused characters (24 chars, 4.6 bits/char)
SAFE_CHARSET='ACDEFGHJKMNPQRTWXY234679'
NUM_CHARSET='0123456789'
SPECIAL_CHARSET='!?@#%~=+'
# ------------------------------------------------------------------------------
# Word filtering (applied when building pool.txt)
# ------------------------------------------------------------------------------
WORD_MIN_LENGTH=4
WORD_MAX_LENGTH=9
# ------------------------------------------------------------------------------
# Templates
# Format: "name|pattern"
#
# Tokens:
# {word} — random word (lowercase)
# {WORD} — random word (UPPERCASE)
# {Word} — random word (Title Case)
# {word!} — random lower or UPPER (+1 bit entropy)
# {Word!} — random lower/Title/UPPER (+1.58 bits entropy)
# {alpha:N} — N chars, Crockford Base32 (5.0 bits/char)
# {safe:N} — N chars, ultra-safe (4.6 bits/char)
# {num:N} — N digits (3.3 bits/char)
# {special:N} — N characters from SPECIAL_CHARSET
# ------------------------------------------------------------------------------
TEMPLATES=(
"default|{word!}{num:2}-{word!}{num:2}-{alpha:3}{num:2}"
"easy|{word}-{alpha:3}-{num:3}"
"secure|{word}-{word}-{alpha:5}-{word}-{alpha:5}{special:1}"
"pin|{num:6}"
"diceware|{word}-{word}-{word}-{word}-{word}"
"wifi|{word!}-{word!}{num:4}"
"phone|{safe:4}-{safe:4}-{safe:4}"
)
# ------------------------------------------------------------------------------
# Default template (used when no template name specified)
# ------------------------------------------------------------------------------
DEFAULT_TEMPLATE="default"
+932
View File
@@ -0,0 +1,932 @@
#!/usr/bin/env bash
set -euo pipefail
# === himi ===
# Memorable secret generator with configurable word lists and templates.
#
# Usage:
# himi rebuild Rebuild pool from enabled lists
# himi Generate secret (default template) → clipboard
# himi <template> Generate secret (named template) → clipboard
# himi -o [template] Output to stdout + clipboard
# himi -n N Generate N secrets
# himi -e [template] Show entropy for template(s)
# himi -h Show help
readonly VERSION="1.0.0"
readonly CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/himi"
readonly CONFIG_FILE="${CONFIG_DIR}/config.sh"
readonly LISTS_DIR="${CONFIG_DIR}/lists"
readonly POOL_DIR="${CONFIG_DIR}/pool"
readonly POOL_FILE="${POOL_DIR}/pool.txt"
# ------------------------------------------------------------------------------
# Defaults (overridden by config.sh)
# ------------------------------------------------------------------------------
ALPHA_CHARSET='0123456789ABCDEFGHJKMNPQRSTVWXYZ' # Crockford Base32 (32 chars, 5 bits)
SAFE_CHARSET='ACDEFGHJKMNPQRTWXY234679' # Ultra-safe (24 chars, 4.6 bits) - no B/8, 0/O, 1/I/L, 5/S
NUM_CHARSET='0123456789'
SPECIAL_CHARSET='!?@#'
WORD_MIN_LENGTH=4
WORD_MAX_LENGTH=8
TEMPLATES=(
"default|{word}-{word}-{alpha:5}"
"easy|{word}-{alpha:3}-{num:3}"
"secure|{word}-{word}-{alpha:5}-{word}-{alpha:5}{special:1}"
"pin|{num:6}"
"diceware|{word}-{word}-{word}-{word}-{word}"
)
DEFAULT_TEMPLATE="default"
# ------------------------------------------------------------------------------
# Colors (disabled if not a terminal)
# ------------------------------------------------------------------------------
if [[ -t 1 ]]; then
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly DIM='\033[0;90m'
readonly BOLD='\033[1m'
readonly NC='\033[0m'
else
readonly RED='' GREEN='' YELLOW='' DIM='' BOLD='' NC=''
fi
# Output helpers
err() { printf "${RED}error:${NC} %s\n" "$1" >&2; }
ok() { printf "${GREEN}✓${NC} %s\n" "$1" >&2; }
warn() { printf "${YELLOW}!${NC} %s\n" "$1" >&2; }
dim() { printf "${DIM}%s${NC}" "$1"; }
# ------------------------------------------------------------------------------
# Platform detection
# ------------------------------------------------------------------------------
detect_platform() {
case "$(uname -s)" in
Darwin*) PLATFORM="macos" ;;
Linux*) PLATFORM="linux" ;;
*) PLATFORM="unknown" ;;
esac
}
# ------------------------------------------------------------------------------
# Cross-platform shuffle (requires GNU coreutils)
# ------------------------------------------------------------------------------
shuffle_lines() {
if command -v shuf &>/dev/null; then
shuf "$@"
elif command -v gshuf &>/dev/null; then
gshuf "$@"
else
err "GNU coreutils required"
if [[ "$PLATFORM" == "macos" ]]; then
printf " Install with: brew install coreutils\n" >&2
else
printf " Install with: sudo apt install coreutils (or equivalent)\n" >&2
fi
exit 1
fi
}
# ------------------------------------------------------------------------------
# Load user config if exists
# ------------------------------------------------------------------------------
load_config() {
if [[ -f "$CONFIG_FILE" ]]; then
source "$CONFIG_FILE"
fi
}
# ------------------------------------------------------------------------------
# Ensure directories exist
# ------------------------------------------------------------------------------
init_dirs() {
mkdir -p "$LISTS_DIR" "$POOL_DIR"
}
# ------------------------------------------------------------------------------
# Filter words: length range + de-duplicate derivatives
# ------------------------------------------------------------------------------
filter_words() {
local min_len="${1:-$WORD_MIN_LENGTH}"
local max_len="${2:-$WORD_MAX_LENGTH}"
grep -E "^[a-z]{${min_len},${max_len}}$" \
| sort -u \
| awk '{
if (prev && index($0, prev) == 1 && length($0) - length(prev) <= 4) next
prev = $0; print
}'
}
# ------------------------------------------------------------------------------
# Add: import and enable a local word list
# ------------------------------------------------------------------------------
cmd_add() {
local file="${1:-}"
if [[ -z "$file" ]]; then
err "usage: himi add <wordlist-file>"
return 1
fi
if [[ ! -f "$file" ]]; then
err "file not found: $file"
return 1
fi
init_dirs
# Derive name from filename (strip path and extension)
local name
name=$(basename "$file" | sed 's/\.[^.]*$//' | tr 'A-Z' 'a-z')
local listfile="${LISTS_DIR}/${name}.txt"
# Clean: lowercase, alpha only, sort, dedupe
tr 'A-Z' 'a-z' < "$file" \
| grep -E '^[a-z]+$' \
| sort -u \
> "$listfile"
local count
count=$(wc -l < "$listfile")
ok "added $name ($count words)"
# Enable by default
cmd_enable "$name"
}
# ------------------------------------------------------------------------------
# Remove: delete list and disable it
# ------------------------------------------------------------------------------
cmd_remove() {
if [[ $# -eq 0 ]]; then
err "usage: himi remove <list>..."
return 1
fi
init_dirs
local had_errors=0
for name in "$@"; do
local listfile="${LISTS_DIR}/${name}.txt"
local linkfile="${POOL_DIR}/${name}.txt"
if [[ ! -f "$listfile" ]]; then
warn "list '$name' not found"
had_errors=1
continue
fi
# Remove symlink if it's currently enabled
[[ -L "$linkfile" ]] && rm "$linkfile"
# Remove the actual list
rm "$listfile"
ok "removed $name"
done
rebuild_pool
return $had_errors
}
# ------------------------------------------------------------------------------
# Enable: symlink list(s) into pool/
# ------------------------------------------------------------------------------
enable_one() {
local name="$1"
local listfile="${LISTS_DIR}/${name}.txt"
local linkfile="${POOL_DIR}/${name}.txt"
if [[ ! -f "$listfile" ]]; then
err "list '$name' not found"
return 1
fi
if [[ -L "$linkfile" ]]; then
warn "'$name' already enabled"
return 0
fi
ln -s "../lists/${name}.txt" "$linkfile"
ok "enabled $name"
}
cmd_enable() {
if [[ $# -eq 0 ]]; then
err "usage: himi enable <list>... | all"
return 1
fi
init_dirs
local had_errors=0
for name in "$@"; do
if [[ "$name" == "all" ]]; then
local f
for f in "$LISTS_DIR"/*.txt; do
[[ -f "$f" ]] || continue
enable_one "$(basename "$f" .txt)" || had_errors=1
done
else
enable_one "$name" || had_errors=1
fi
done
rebuild_pool
return $had_errors
}
# ------------------------------------------------------------------------------
# Disable: remove symlink(s) from pool/
# ------------------------------------------------------------------------------
disable_one() {
local name="$1"
local linkfile="${POOL_DIR}/${name}.txt"
if [[ ! -L "$linkfile" ]]; then
warn "'$name' not enabled"
return 0
fi
rm "$linkfile"
ok "disabled $name"
}
cmd_disable() {
if [[ $# -eq 0 ]]; then
err "usage: himi disable <list>... | all"
return 1
fi
init_dirs
local had_errors=0
for name in "$@"; do
if [[ "$name" == "all" ]]; then
local f
for f in "$POOL_DIR"/*.txt; do
[[ -L "$f" ]] || continue
disable_one "$(basename "$f" .txt)" || had_errors=1
done
else
disable_one "$name" || had_errors=1
fi
done
rebuild_pool
return $had_errors
}
# ------------------------------------------------------------------------------
# Rebuild: regenerate pool.txt from enabled lists
# ------------------------------------------------------------------------------
cmd_rebuild() {
init_dirs
rebuild_pool
}
# ------------------------------------------------------------------------------
# Rebuild pool from enabled lists (symlinks in pool/)
# ------------------------------------------------------------------------------
rebuild_pool() {
local before=0
[[ -f "$POOL_FILE" ]] && before=$(wc -l < "$POOL_FILE")
# Only include symlinked files (enabled lists), exclude pool.txt itself
local enabled_lists
enabled_lists=$(find "$POOL_DIR" -maxdepth 1 -type l -name '*.txt' 2>/dev/null)
if [[ -z "$enabled_lists" ]]; then
warn "no lists enabled"
> "$POOL_FILE"
return
fi
cat $enabled_lists \
| filter_words "$WORD_MIN_LENGTH" "$WORD_MAX_LENGTH" \
> "$POOL_FILE"
local after
after=$(wc -l < "$POOL_FILE")
local diff=$((after - before))
if [[ $before -eq 0 ]]; then
ok "pool: $after words $(dim "($(calc_log2 "$after") bits/word)")"
elif [[ $diff -ne 0 ]]; then
local sign="+"
[[ $diff -lt 0 ]] && sign=""
ok "pool: $after words $(dim "($(calc_log2 "$after") bits/word)") [${sign}${diff}]"
fi
if [[ "$after" -gt 0 && "$after" -lt 256 ]]; then
warn "pool has only $after words ($(calc_log2 "$after") bits/word) — secrets will be weak"
warn "enable more lists with 'himi enable' or add lists with 'himi add'"
fi
}
# ------------------------------------------------------------------------------
# List: show all lists and enabled status
# ------------------------------------------------------------------------------
cmd_list() {
init_dirs
if ! ls "${LISTS_DIR}"/*.txt &>/dev/null; then
warn "no word lists found — run 'himi add <file>'"
return 0
fi
printf " %-16s %7s\n" "LIST" "WORDS"
printf " %-16s %7s\n" "────────────────" "───────"
for f in "${LISTS_DIR}"/*.txt; do
local name count marker
name=$(basename "$f" .txt)
count=$(wc -l < "$f")
if [[ -L "${POOL_DIR}/${name}.txt" ]]; then
marker="${GREEN}●${NC}"
else
marker="${DIM}○${NC}"
fi
printf " %b %-16s %7d\n" "$marker" "$name" "$count"
done
printf " %-16s %7s\n" "────────────────" "───────"
if [[ -f "$POOL_FILE" ]]; then
local pool_count
pool_count=$(wc -l < "$POOL_FILE")
if [[ "$pool_count" -gt 0 ]]; then
printf " %-16s %7d ${DIM}(%.1f bits/word)${NC}\n" "pool" "$pool_count" "$(calc_log2 "$pool_count")"
else
printf " %-16s %7d ${DIM}(empty)${NC}\n" "pool" "0"
fi
fi
}
# ------------------------------------------------------------------------------
# Uninstall: remove himi and all data
# ------------------------------------------------------------------------------
cmd_uninstall() {
read -p "Are you sure you want to remove himi and all custom wordlists? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return 1
fi
# 1. Clean up Shell Profiles
local profiles=(".bashrc" ".zshrc" ".bash_profile" ".profile")
local start_marker="# >>> himi initialize >>>"
local end_marker="# <<< himi initialize <<<"
for p in "${profiles[@]}"; do
if [[ -f "$HOME/$p" ]]; then
# Uses sed to delete everything between the markers (inclusive)
if grep -q "$start_marker" "$HOME/$p"; then
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS requires the extension to be attached to the -i flag
sed -i '.bak' "/$start_marker/,/$end_marker/d" "$HOME/$p"
else
# Linux/GNU
sed -i.bak "/$start_marker/,/$end_marker/d" "$HOME/$p"
fi
ok "Cleaned up $p (backup created as $p.bak)"
fi
fi
done
# 2. Remove completions
rm -f "${HOME}/.zfunc/_himi"
rm -f "${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions/himi"
# 3. Remove data and binary
rm -rf "$CONFIG_DIR"
ok "Removed configuration and wordlists."
# Final step: Binary deletes itself
local self
self=$(command -v himi)
rm -f "$self"
ok "himi has been uninstalled."
}
# ------------------------------------------------------------------------------
# Calculate log base 2 using awk
# ------------------------------------------------------------------------------
calc_log2() {
awk -v n="$1" 'BEGIN { printf "%.2f", log(n)/log(2) }'
}
# ------------------------------------------------------------------------------
# Get charset length
# ------------------------------------------------------------------------------
charset_len() {
printf "%s" "$1" | wc -c
}
# ------------------------------------------------------------------------------
# Calculate entropy for a template string
# ------------------------------------------------------------------------------
calc_entropy() {
local template="$1"
local entropy=0
local pool_size word_bits
pool_size=$(wc -l < "$POOL_FILE" 2>/dev/null || echo 0)
[[ "$pool_size" -eq 0 ]] && { echo "0"; return; }
word_bits=$(calc_log2 "$pool_size")
# Count word variants (most specific first)
# {word!} = pool + 1 bit (2 cases)
local word_bang_count
word_bang_count=$(grep -o '{word!}' <<< "$template" | wc -l)
word_bang_count="${word_bang_count:-0}"
if [[ "$word_bang_count" -gt 0 ]]; then
entropy=$(awk -v e="$entropy" -v w="$word_bang_count" -v b="$word_bits" 'BEGIN { print e + w * (b + 1) }')
template="${template//\{word!\}/}"
fi
# {Word!} = pool + log2(3) bits (3 cases)
local Word_bang_count
Word_bang_count=$(grep -o '{Word!}' <<< "$template" | wc -l)
Word_bang_count="${Word_bang_count:-0}"
if [[ "$Word_bang_count" -gt 0 ]]; then
entropy=$(awk -v e="$entropy" -v w="$Word_bang_count" -v b="$word_bits" 'BEGIN { print e + w * (b + log(3)/log(2)) }')
template="${template//\{Word!\}/}"
fi
# {WORD}, {Word}, {word} = pool bits only (no case entropy)
local WORD_count Word_count word_count
WORD_count=$(grep -o '{WORD}' <<< "$template" | wc -l)
WORD_count="${WORD_count:-0}"
template="${template//\{WORD\}/}"
Word_count=$(grep -o '{Word}' <<< "$template" | wc -l)
Word_count="${Word_count:-0}"
template="${template//\{Word\}/}"
word_count=$(grep -o '{word}' <<< "$template" | wc -l)
word_count="${word_count:-0}"
local total_plain_words=$(( WORD_count + Word_count + word_count ))
if [[ "$total_plain_words" -gt 0 ]]; then
entropy=$(awk -v e="$entropy" -v w="$total_plain_words" -v b="$word_bits" 'BEGIN { print e + (w * b) }')
fi
# Count {alpha:N} - extract N and calculate
while [[ "$template" =~ \{alpha:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local charset_size
charset_size=$(charset_len "$ALPHA_CHARSET")
local bits
bits=$(awk -v n="$n" -v cs="$charset_size" 'BEGIN { printf "%.2f", n * (log(cs)/log(2)) }')
entropy=$(awk -v e="$entropy" -v b="$bits" 'BEGIN { print e + b }')
template="${template/\{alpha:$n\}/}"
done
# Count {num:N}
while [[ "$template" =~ \{num:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local charset_size
charset_size=$(charset_len "$NUM_CHARSET")
local bits
bits=$(awk -v n="$n" -v cs="$charset_size" 'BEGIN { printf "%.2f", n * (log(cs)/log(2)) }')
entropy=$(awk -v e="$entropy" -v b="$bits" 'BEGIN { print e + b }')
template="${template/\{num:$n\}/}"
done
# Count {special:N}
while [[ "$template" =~ \{special:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local charset_size
charset_size=$(charset_len "$SPECIAL_CHARSET")
local bits
bits=$(awk -v n="$n" -v cs="$charset_size" 'BEGIN { printf "%.2f", n * (log(cs)/log(2)) }')
entropy=$(awk -v e="$entropy" -v b="$bits" 'BEGIN { print e + b }')
template="${template/\{special:$n\}/}"
done
# Count {safe:N}
while [[ "$template" =~ \{safe:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local charset_size
charset_size=$(charset_len "$SAFE_CHARSET")
local bits
bits=$(awk -v n="$n" -v cs="$charset_size" 'BEGIN { printf "%.2f", n * (log(cs)/log(2)) }')
entropy=$(awk -v e="$entropy" -v b="$bits" 'BEGIN { print e + b }')
template="${template/\{safe:$n\}/}"
done
printf "%.1f" "$entropy"
}
# ------------------------------------------------------------------------------
# Pick N random words from pool
# ------------------------------------------------------------------------------
pick_words() {
local n="${1:-1}"
shuffle_lines -n "$n" "$POOL_FILE"
}
# ------------------------------------------------------------------------------
# Generate random string from charset
# ------------------------------------------------------------------------------
rand_chars() {
local charset="$1"
local count="$2"
LC_ALL=C tr -dc "$charset" < /dev/urandom 2>/dev/null | head -c "$count"
}
# ------------------------------------------------------------------------------
# Portable case conversion (works on bash 3.2/macOS)
# ------------------------------------------------------------------------------
to_upper() { tr '[:lower:]' '[:upper:]'; }
to_lower() { tr '[:upper:]' '[:lower:]'; }
to_title() {
local w
read -r w
printf "%s%s" "$(printf '%s' "${w:0:1}" | to_upper)" "$(printf '%s' "${w:1}" | to_lower)"
}
# ------------------------------------------------------------------------------
# Expand a template into a secret
# ------------------------------------------------------------------------------
expand_template() {
local template="$1"
local result="$template"
local word
# Process word tokens (most specific first to avoid partial matches)
# {word!} - random lower/UPPER (+1 bit)
while [[ "$result" =~ \{word!\} ]]; do
word=$(pick_words 1)
if (( RANDOM % 2 )); then
word=$(printf '%s' "$word" | to_upper)
fi
result="${result/\{word!\}/$word}"
done
# {Word!} - random lower/Title/UPPER (+1.58 bits)
while [[ "$result" =~ \{Word!\} ]]; do
word=$(pick_words 1)
case $(( RANDOM % 3 )) in
1) word=$(printf '%s' "$word" | to_title) ;;
2) word=$(printf '%s' "$word" | to_upper) ;;
esac
result="${result/\{Word!\}/$word}"
done
# {WORD} - all upper
while [[ "$result" =~ \{WORD\} ]]; do
word=$(pick_words 1 | to_upper)
result="${result/\{WORD\}/$word}"
done
# {Word} - title case
while [[ "$result" =~ \{Word\} ]]; do
word=$(pick_words 1 | to_title)
result="${result/\{Word\}/$word}"
done
# {word} - all lower (default)
while [[ "$result" =~ \{word\} ]]; do
word=$(pick_words 1)
result="${result/\{word\}/$word}"
done
# Replace {alpha:N}
while [[ "$result" =~ \{alpha:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local chars
chars=$(rand_chars "$ALPHA_CHARSET" "$n")
result="${result/\{alpha:$n\}/$chars}"
done
# Replace {num:N}
while [[ "$result" =~ \{num:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local chars
chars=$(rand_chars "$NUM_CHARSET" "$n")
result="${result/\{num:$n\}/$chars}"
done
# Replace {special:N}
while [[ "$result" =~ \{special:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local chars
chars=$(rand_chars "$SPECIAL_CHARSET" "$n")
result="${result/\{special:$n\}/$chars}"
done
# Replace {safe:N}
while [[ "$result" =~ \{safe:([0-9]+)\} ]]; do
local n="${BASH_REMATCH[1]}"
local chars
chars=$(rand_chars "$SAFE_CHARSET" "$n")
result="${result/\{safe:$n\}/$chars}"
done
printf "%s" "$result"
}
# ------------------------------------------------------------------------------
# Get template string by name
# ------------------------------------------------------------------------------
get_template() {
local name="$1"
for entry in "${TEMPLATES[@]}"; do
IFS='|' read -r tname tpattern <<< "$entry"
if [[ "$tname" == "$name" ]]; then
printf "%s" "$tpattern"
return 0
fi
done
err "unknown template '$name'"
return 1
}
# ------------------------------------------------------------------------------
# Copy to clipboard (cross-platform)
# ------------------------------------------------------------------------------
to_clipboard() {
case "$PLATFORM" in
macos)
pbcopy
;;
linux)
if command -v xclip &>/dev/null; then
xclip -selection clipboard
elif command -v xsel &>/dev/null; then
xsel --clipboard --input
elif command -v wl-copy &>/dev/null; then
wl-copy
else
cat # fallback: output to stdout
warn "install xclip, xsel, or wl-copy for clipboard"
return 1
fi
;;
*)
cat # fallback: output to stdout
warn "clipboard not supported on this platform"
return 1
;;
esac
}
# ------------------------------------------------------------------------------
# Show entropy for templates
# ------------------------------------------------------------------------------
cmd_entropy() {
local specific="${1:-}"
if [[ ! -f "$POOL_FILE" ]] || [[ ! -s "$POOL_FILE" ]]; then
err "pool empty — run 'himi rebuild' first"
return 1
fi
printf "%-12s %-40s %s\n" "TEMPLATE" "EXAMPLE" "ENTROPY"
printf "%-12s %-40s %s\n" "--------" "-------" "-------"
for entry in "${TEMPLATES[@]}"; do
IFS='|' read -r name pattern <<< "$entry"
# If specific template requested, skip others
[[ -n "$specific" && "$name" != "$specific" ]] && continue
local example entropy
example=$(expand_template "$pattern")
entropy=$(calc_entropy "$pattern")
printf "%-12s %-40s ≈%s bits\n" "$name" "$example" "$entropy"
done
}
# ------------------------------------------------------------------------------
# Config: open config file in editor
# ------------------------------------------------------------------------------
cmd_config() {
local editor="${EDITOR:-${VISUAL:-vi}}"
if [[ ! -f "$CONFIG_FILE" ]]; then
err "config not found: $CONFIG_FILE"
return 1
fi
local before after
before=$(cksum "$CONFIG_FILE")
"$editor" "$CONFIG_FILE"
after=$(cksum "$CONFIG_FILE")
if [[ "$before" != "$after" ]]; then
ok "config changed — rebuilding pool"
load_config
rebuild_pool
fi
}
# ------------------------------------------------------------------------------
# Test: one-off pattern test with entropy comparison
# ------------------------------------------------------------------------------
cmd_test() {
local pattern="${1:-}"
if [[ -z "$pattern" ]]; then
err "usage: himi test '{pattern}'"
printf "\n Example: himi test '{word!}-{num:2}-{word!}'\n"
return 1
fi
if [[ ! -f "$POOL_FILE" ]] || [[ ! -s "$POOL_FILE" ]]; then
err "pool empty — run 'himi rebuild' first"
return 1
fi
local entropy
entropy=$(calc_entropy "$pattern")
printf "\n"
printf " ${BOLD}Pattern:${NC} %s\n" "$pattern"
printf " ${BOLD}Entropy:${NC} ≈%s bits\n" "$entropy"
printf "\n"
printf " ${BOLD}Examples:${NC}\n"
for ((i = 0; i < 5; i++)); do
printf " %s\n" "$(expand_template "$pattern")"
done
printf "\n"
# Show comparison with built-in templates
printf " ${DIM}%-12s %s${NC}\n" "TEMPLATE" "ENTROPY"
printf " ${DIM}%-12s %s${NC}\n" "────────────" "───────"
printf " ${BOLD}%-12s ≈%s bits${NC} ← your pattern\n" "(test)" "$entropy"
for entry in "${TEMPLATES[@]}"; do
IFS='|' read -r tname tpattern <<< "$entry"
local tent
tent=$(calc_entropy "$tpattern")
printf " %-12s ≈%s bits\n" "$tname" "$tent"
done
printf "\n"
}
# ------------------------------------------------------------------------------
# Generate secrets
# ------------------------------------------------------------------------------
cmd_generate() {
local template_name="${1:-$DEFAULT_TEMPLATE}"
local count="${2:-1}"
local use_stdout="${3:-false}"
if [[ ! -f "$POOL_FILE" ]] || [[ ! -s "$POOL_FILE" ]]; then
err "pool empty — run 'himi rebuild' first"
return 1
fi
# Sanity check: ensure pool has enough unique words for meaningful entropy
local unique_words
unique_words=$(sort -u "$POOL_FILE" | wc -l)
if [[ "$unique_words" -lt 256 ]]; then
err "pool has only $unique_words unique words — refusing to generate (need ≥256)"
warn "run 'himi rebuild' or 'himi enable' to fix"
return 1
fi
local pattern
pattern=$(get_template "$template_name") || return 1
local secrets=""
for ((i = 0; i < count; i++)); do
[[ -n "$secrets" ]] && secrets+=$'\n'
secrets+=$(expand_template "$pattern")
done
# Always copy to clipboard
if printf "%s" "$secrets" | to_clipboard; then
ok "copied to clipboard"
fi
# Also print to stdout if -o was passed
if [[ "$use_stdout" == "true" ]]; then
printf "%s\n" "$secrets"
fi
}
# ------------------------------------------------------------------------------
# Show help
# ------------------------------------------------------------------------------
cmd_help() {
cat <<EOF
himi v${VERSION} — memorable secret generator
Usage:
himi [template] Generate → clipboard (default: $DEFAULT_TEMPLATE)
himi -o [template] Generate → clipboard + stdout
himi -n N [template] Generate N secrets
himi add <file> Add and enable a word list
himi enable <list>... Enable word list(s), or 'all'
himi disable <list>... Disable word list(s), or 'all'
himi list Show all lists and status
himi rebuild Rebuild pool from enabled lists
himi entropy [name] Show entropy for template(s)
himi test '{pattern}' Test a custom pattern
himi config Edit config in \$EDITOR
himi uninstall Remove himi and all data
himi -h, --help Show this help
Templates (configure in $CONFIG_FILE):
EOF
for entry in "${TEMPLATES[@]}"; do
IFS='|' read -r name pattern <<< "$entry"
printf " %-12s %s\n" "$name" "$pattern"
done
cat <<EOF
Tokens:
{word} Random word (lowercase)
{WORD} Random word (UPPERCASE)
{Word} Random word (Title Case)
{word!} Random word (lower or UPPER, +1 bit)
{Word!} Random word (lower/Title/UPPER, +1.58 bits)
{alpha:N} N chars, Crockford Base32 (5.0 bits/char)
{safe:N} N chars, ultra-safe (4.6 bits/char)
{num:N} N digits (3.3 bits/char)
{special:N} N from: $SPECIAL_CHARSET
Config: $CONFIG_FILE
Pool: $POOL_FILE
EOF
}
# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
main() {
detect_platform
load_config
init_dirs
# 1. Intercept top-level commands first
case "${1:-}" in
add|enable|disable|remove|rebuild|list|test|config|entropy|uninstall)
local cmd="cmd_$1"
shift
"$cmd" "$@" # Pass all remaining arguments correctly
return $?
;;
esac
# 2. If it's not a command, parse generation flags and templates
local use_stdout="false"
local count=1
local template_name=""
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--stdout)
use_stdout="true"
shift
;;
-n|--number)
if [[ -z "${2:-}" || ! "${2:-}" =~ ^[1-9][0-9]*$ ]]; then
err "-n requires a positive integer (got: '${2:-}')"
return 1
fi
count="$2"
shift 2
;;
-h|--help|help)
cmd_help
return 0
;;
-*)
err "Unknown option: $1"
cmd_help >&2
return 1
;;
*)
if [[ -n "$template_name" ]]; then
err "Unexpected argument: $1. Did you mean to use a command?"
return 1
fi
template_name="$1"
shift
;;
esac
done
[[ -z "$template_name" ]] && template_name="$DEFAULT_TEMPLATE"
cmd_generate "$template_name" "$count" "$use_stdout"
}
main "$@"
+127
View File
@@ -0,0 +1,127 @@
# himi bash completion
# Source this or drop in ~/.local/share/bash-completion/completions/
_himi() {
local cur prev words cword
# Compatibility for older bash
if type _init_completion &>/dev/null; then
_init_completion || return
else
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
cword=$COMP_CWORD
words=("${COMP_WORDS[@]}")
fi
local commands="add enable disable remove rebuild list entropy test config uninstall"
local flags="-o -n -h --help"
local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/himi"
local lists_dir="${config_dir}/lists"
local pool_dir="${config_dir}/pool"
# Check if we're in a multi-arg command (enable/disable)
local cmd=""
local i
for ((i=1; i < cword; i++)); do
case "${words[i]}" in
enable|disable|add|entropy|test|config)
cmd="${words[i]}"
break
;;
esac
done
case "$cmd" in
enable)
# Show disabled lists + 'all' keyword
if [[ -d "$lists_dir" ]]; then
local available="all "
local f
for f in "$lists_dir"/*.txt; do
[[ -f "$f" ]] || continue
local name=$(basename "$f" .txt)
[[ ! -L "$pool_dir/$name.txt" ]] && available+="$name "
done
COMPREPLY=($(compgen -W "$available" -- "$cur"))
fi
return
;;
disable)
# Show enabled lists + 'all' keyword
if [[ -d "$pool_dir" ]]; then
local enabled="all "
local f
for f in "$pool_dir"/*.txt; do
[[ -L "$f" ]] || continue
enabled+="$(basename "$f" .txt) "
done
COMPREPLY=($(compgen -W "$enabled" -- "$cur"))
fi
return
;;
remove)
if [[ -d "$lists_dir" ]]; then
local lists=""
for f in "$lists_dir"/*.txt; do
[[ -f "$f" ]] || continue
lists+="$(basename "$f" .txt) "
done
COMPREPLY=($(compgen -W "$lists" -- "$cur"))
fi
return
;;
add)
COMPREPLY=($(compgen -f -X '!*.txt' -- "$cur"))
return
;;
entropy)
local templates="default easy secure pin diceware"
if [[ -f "${config_dir}/config.sh" ]]; then
local parsed
parsed=$(grep -E '^\s*"[a-z]+\|' "${config_dir}/config.sh" 2>/dev/null | sed 's/.*"\([a-z]*\)|.*/\1/' | tr '\n' ' ')
[[ -n "$parsed" ]] && templates="$parsed"
fi
COMPREPLY=($(compgen -W "$templates" -- "$cur"))
return
;;
test|config)
# No completion for test (user types pattern) or config (no args)
return
;;
esac
# Handle flags
case "$prev" in
-n)
COMPREPLY=($(compgen -W "1 3 5 10" -- "$cur"))
return
;;
-o)
local templates="default easy secure pin diceware"
if [[ -f "${config_dir}/config.sh" ]]; then
local parsed
parsed=$(grep -E '^\s*"[a-z]+\|' "${config_dir}/config.sh" 2>/dev/null | sed 's/.*"\([a-z]*\)|.*/\1/' | tr '\n' ' ')
[[ -n "$parsed" ]] && templates="$parsed"
fi
COMPREPLY=($(compgen -W "$templates" -- "$cur"))
return
;;
esac
# First argument: commands, flags, or templates
if [[ ${cword} -eq 1 ]]; then
local templates="default easy secure pin diceware"
if [[ -f "${config_dir}/config.sh" ]]; then
local parsed
parsed=$(grep -E '^\s*"[a-z]+\|' "${config_dir}/config.sh" 2>/dev/null | sed 's/.*"\([a-z]*\)|.*/\1/' | tr '\n' ' ')
[[ -n "$parsed" ]] && templates="$parsed"
fi
COMPREPLY=($(compgen -W "$commands $flags $templates" -- "$cur"))
return
fi
}
complete -F _himi himi
Executable
+195
View File
@@ -0,0 +1,195 @@
#!/usr/bin/env bash
set -euo pipefail
# himi installer
# Installs the himi CLI, word lists, and shell completions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BIN_DIR="${HOME}/.local/bin"
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/himi"
BASH_COMPLETION_DIR="${XDG_DATA_HOME:-$HOME/.local/share/bash-completion/completions}"
ZSH_COMPLETION_DIR="${HOME}/.zfunc"
# Define markers for removal later
MARKER_START="# >>> himi initialize >>>"
MARKER_END="# <<< himi initialize <<<"
# Colors
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
DIM='\033[0;90m'
NC='\033[0m'
else
RED='' GREEN='' YELLOW='' DIM='' NC=''
fi
err() { printf "${RED}error:${NC} %s\n" "$1" >&2; }
ok() { printf "${GREEN}${NC} %s\n" "$1" >&2; }
warn() { printf "${YELLOW}!${NC} %s\n" "$1" >&2; }
dim() { printf "${DIM}%s${NC}" "$1"; }
show_splash() {
local splash="${SCRIPT_DIR}/splash.txt"
[[ -f "$splash" ]] || return 0
if [[ -t 1 ]]; then
printf '\033[38;5;197m'
while IFS= read -r line; do
printf '%s\n' "$line"
sleep 0.02
done < "$splash"
printf '\033[0m'
sleep 1
else
cat "$splash"
echo
fi
}
detect_shell() {
# Check the parent process or SHELL env var instead of local vars
if [[ "${SHELL:-}" == */zsh ]]; then
echo "zsh"
elif [[ "${SHELL:-}" == */bash ]]; then
echo "bash"
else
echo "bash" # Safe default
fi
}
check_deps() {
dim "Checking dependencies... "
if ! command -v shuf &>/dev/null && ! command -v gshuf &>/dev/null; then
echo
err "himi requires GNU coreutils (shuf)."
[[ "$OSTYPE" == "darwin"* ]] && warn "Install with: brew install coreutils"
exit 1
fi
echo "done."
}
install_binary() {
[[ -f "${SCRIPT_DIR}/himi" ]] || { err "himi binary not found in installer directory"; return 1; }
mkdir -p "$BIN_DIR"
if cp "${SCRIPT_DIR}/himi" "$BIN_DIR/"; then
chmod +x "$BIN_DIR/himi"
ok "Installed himi to $BIN_DIR"
else
err "Failed to copy himi to $BIN_DIR"
return 1
fi
}
install_config() {
mkdir -p "${CONFIG_DIR}/lists" "${CONFIG_DIR}/pool"
if [[ -f "${SCRIPT_DIR}/config.sh" ]]; then
if [[ ! -f "${CONFIG_DIR}/config.sh" ]]; then
cp "${SCRIPT_DIR}/config.sh" "${CONFIG_DIR}/"
ok "Installed default config to $CONFIG_DIR"
else
dim "Config already exists, skipping.\n"
fi
else
err "config.sh missing from installer directory"
return 1
fi
}
install_lists() {
local lists_src="${SCRIPT_DIR}/lists"
local count=0
if [[ -d "$lists_src" ]]; then
for f in "${lists_src}"/*.txt; do
[[ -f "$f" ]] || continue
# Use count+=1 to avoid the exit code 1 issue with ((count++)) when count is 0
cp "$f" "${CONFIG_DIR}/lists/" && ((count+=1)) || true
done
ok "Installed $count wordlists"
fi
# Enable defaults
local enabled=0
for name in eff bips; do
if [[ -f "${CONFIG_DIR}/lists/${name}.txt" ]]; then
ln -sf "../lists/${name}.txt" "${CONFIG_DIR}/pool/${name}.txt" && ((enabled+=1)) || true
fi
done
[[ $enabled -gt 0 ]] && ok "Enabled default lists (eff, bips)"
}
install_completions() {
local shell_type=$(detect_shell)
local profile=""
local comp_src=""
local comp_dest=""
local installed=""
case "$shell_type" in
bash)
profile="${HOME}/.bashrc"
comp_src="${SCRIPT_DIR}/himi.bash"
comp_dest="${BASH_COMPLETION_DIR}/himi"
mkdir -p "$BASH_COMPLETION_DIR"
;;
zsh)
profile="${HOME}/.zshrc"
comp_src="${SCRIPT_DIR}/_himi"
comp_dest="${ZSH_COMPLETION_DIR}/_himi"
mkdir -p "$ZSH_COMPLETION_DIR"
;;
esac
if [[ -n "$comp_src" && -f "$comp_src" ]]; then
if cp "$comp_src" "$comp_dest"; then
installed="$shell_type"
fi
fi
[[ -z "$installed" ]] && return
# Shell Environment Hook
touch "$profile"
if ! grep -q "$MARKER_START" "$profile"; then
{
printf "\n%s\n" "$MARKER_START"
printf 'export PATH="${HOME}/.local/bin:${PATH}"\n'
if [[ "$installed" == "bash" ]]; then
printf '[[ -f "%s" ]] && source "%s"\n' "$comp_dest" "$comp_dest"
else
printf 'fpath=(%s $fpath)\nautoload -Uz compinit && compinit\n' "$ZSH_COMPLETION_DIR"
fi
printf "%s\n" "$MARKER_END"
} >> "$profile"
ok "Updated $profile with PATH and completions"
else
ok "Completions already configured in $profile"
fi
}
main() {
show_splash
printf "Installing himi...\n\n"
check_deps
install_binary
install_config
install_lists
install_completions
# Initialize the pool so the tool works immediately
dim "Initializing wordlist pool... "
if "${BIN_DIR}/himi" rebuild >/dev/null 2>&1; then
echo "done."
else
echo "failed."
warn "Initial pool rebuild failed. You may need to run 'himi rebuild' manually."
fi
printf "\n${GREEN}Done!${NC} Restart your terminal or run: source %s\n" "$( [[ "$(detect_shell)" == "zsh" ]] && echo '~/.zshrc' || echo '~/.bashrc' )"
}
main "$@"
+1109
View File
File diff suppressed because it is too large Load Diff
+2048
View File
File diff suppressed because it is too large Load Diff
+5611
View File
File diff suppressed because it is too large Load Diff
+7776
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1307
View File
File diff suppressed because it is too large Load Diff
+445
View File
@@ -0,0 +1,445 @@
aceso
achelous
acheron
achlys
achos
acis
acmon
acratopotes
actaeus
acte
adephagia
adikia
adonis
adrasteia
aegaeon
aegle
aeolus
aergia
aether
aetna
agathodaemon
agdistis
aglaea
agon
aiakos
aidos
aion
aisa
aitnaios
alala
alastor
alcyone
alecto
alectrona
aletheia
alexiares
alke
alkon
alpheus
amechania
amphiaraus
amphictyonis
amphitrite
anaideia
ananke
anatolia
angelia
angelos
ania
anicetus
anteros
antheia
anthousai
aoide
aparctias
apate
aphaea
apheleia
apheliotes
aphrodite
aphroditus
apollo
apollonis
arche
ares
arete
arethusa
argestes
argyron
aristaeus
arke
arktos
artemis
asbetos
asclepius
askalaphos
asopus
asteria
astraea
astraeus
astraios
atabyrius
atë
athena
atlas
atropos
attis
auge
aura
aurai
auxesia
auxo
benthesikyme
bia
boreas
borysthenis
britomartis
brizo
caerus
caicias
calleis
calliope
celaeno
cephisso
ceraon
ceto
chalcon
chaos
charon
cheimon
chione
chloris
chronos
chryson
chrysus
circe
circios
cladeus
cleta
clio
clotho
coeus
comus
corus
corymbus
crius
cronus
cyamites
cybele
cyllenus
cymopoleia
damia
damnameneus
damon
daphne
deimos
deipneus
delas
demeter
despoina
dexithea
dikaiosyne
dike
dione
dionysus
dolos
dryades
dynamene
dysis
dysnomia
dyssebeia
echo
eiar
eidothea
eileithyia
eirene
eiresione
ekecheiria
electra
eleos
elpis
empusa
enyalius
enyo
eos
eosphorus
epiales
epidotes
epimedes
epimeliades
epimetheus
epione
epiphron
erato
erebos
erebus
eridanos
eris
eros
ersa
eucleia
eudaimonia
eulabeia
eunomia
eunostus
eupheme
euphrosyne
euporie
eupraxia
euronotus
eurotas
eurus
eurymedon
eusebeia
euterpe
euthenia
euthymia
gaia
galene
gelos
geras
glaucus
glycon
gorgyra
gymnastica
hades
hamadryades
harmonia
harpocrates
hebe
hecate
hecaterus
hedone
hedylogos
hegemone
heimarmene
hekaerge
helios
hemera
hephaestus
hera
heracles
hermaphroditus
hermes
hesperis
hesperus
hestia
himeros
homados
homonoia
horkos
horme
hybris
hygieia
hymenaios
hypate
hyperion
hypnos
iapetus
iasios
iaso
ichnaea
idyia
ioke
iris
iynx
kakia
karpo
kelmis
keuthonymos
koalemos
kokytos
kratos
kydoimos
lachesis
lelantos
lethe
leto
leucothea
limos
lips
loxo
lupe
lycos
lysagora
lyssa
macaria
maenades
maia
makelo
mania
matton
megaera
megalesius
melete
meliae
melinoe
melpomene
men
menoetes
menoetius
merope
mese
mesembria
methe
metis
metope
minos
minthe
mneme
mnemosyne
momus
moros
morpheus
musica
mylas
name-english
nemesis
nephele
nereus
nerites
nesoi
nete
nike
nikon
nilus
nomos
notus
nymphe
nyx
oceanus
oizys
omodamos
oneiroi
onnes
ormenos
orphne
orthosie
oupis
ourea
paean
paidia
palaemon
palaestra
palioxis
pallas
pan
panacea
pandaisia
pandia
pannychis
pasithea
peitharchia
peitho
peneus
penia
penthus
pepromene
persephone
perses
phaenna
phaenon
phaethon
phanes
pheme
pherousa
philomelus
philophrosyne
philotes
phlegethon
phobos
phoebe
phorcys
phrike
phthonus
pistis
plutus
poine
polemos
polyhymnia
polymatheia
ponos
pontos
pontus
poros
poseidon
pothos
praxidike
priapus
proioxis
prometheus
prophasis
proteus
psamathe
pthinoporon
ptocheia
pyroeis
pyrrhichos
rhadamanthys
rhapso
rhea
sabaktes
sabazios
sangarius
scamander
selene
silenus
simon
skeiron
skelmis
skythes
smaragos
sophrosyne
soter
soteria
sponde
sterope
stilbon
styx
syntribos
tartarus
taygete
telesphorus
telete
terpsichore
tethys
thalassa
thalia
thallo
thanatos
thaumas
theamphilogiai
theandroktasiai
thearae
thehesperides
thehysminai
theia
thekeres
thelitae
thelxinoe
themachai
themis
theneikea
theoneiroi
theourea
thepalici
thephonoi
theros
thetis
thoosa
thrasos
tisiphone
titias
tonnes
triptolemus
triteia
triton
tritones
tyche
urania
uranus
zagreus
zelos
zephyrus
zeus
+1403
View File
File diff suppressed because it is too large Load Diff
+1024
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
.x$xxx.
$+++++++++$
$$+++++++X$$$
;;++++++;+;+++
x:+;;;;+:;;+++
+;;$$X$$:$$;+++
+;:X$$x$$$$;++: x
+::::$$$$:;;+ x;:
; :$x+$$$; ;: ;;
;;;+:$;$xx$;;$$X;;; ;:XX+x;
;;:;;X;+:$$$$$$$$;;;;;: ;:+;;X;;
;;;;;;;;:;::::;;;;;;;:;; ;;:+:+;:;
+;;;;;;;;:;;;;;;;;;;;;;;;;; ;;;;;;:
:+;+++;;;;;;;x;;;;:;;;;;;;;;;: ;;;;
$$$x;+X+;;;;;x$$;;;;;;;;;;;;;+X ;;:
$$$xx;+Xxx++x$$$x+++++;XX++XX++x ;;;
$$$$$;;:+xx$x$$$xxXx+++xX++;X; $xx
:$$$$$;;;+x$$$$$xxxxxxX;;$$$$$$$$$$x;+x
;$$$$$;$$$+$$x$$$$$$$x:;$$$$$$$xxx:$$$
;X$$$$x$xX$$x$$x$$$$$$$;;;;:$$$xxxxx:
;$$x$$$$$$x$$$$X$$$$$$$;;;;;; x;
;$$x$$$$$$x$$$$X$$$$$$;;;;; x.
##. .## ####.##. ..##.####.###### .##### .##. ##
## ..## ##. ####.#### ##. .##. ## . . ## .##
.#######. ## .##.### ##. ## .## . ##### ## .##
.##. ##..## .## .# .##. ## . ##. # . ## ## .##
.## .##.#### ##. ..## ####. .##. ##### .#####