karnel-termux 4.17.9 → 4.17.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +30 -12
  2. package/docs/ARCHITECTURE/index.md +7 -1
  3. package/docs/CHANGELOG.md +52 -0
  4. package/docs/_config.yml +0 -2
  5. package/docs/_layouts/base.html +21 -0
  6. package/docs/assets/css/style.scss +34 -0
  7. package/docs/cli/index.md +13 -7
  8. package/docs/doctor/index.md +6 -0
  9. package/docs/index.md +14 -13
  10. package/docs/karnel/ai/index.md +17 -0
  11. package/docs/karnel/auto/index.md +17 -0
  12. package/docs/karnel/db/index.md +17 -0
  13. package/docs/karnel/deploy/index.md +17 -0
  14. package/docs/karnel/dev/index.md +17 -0
  15. package/docs/karnel/editor/index.md +17 -0
  16. package/docs/karnel/games/index.md +17 -0
  17. package/docs/karnel/lang/index.md +17 -0
  18. package/docs/karnel/network/index.md +17 -0
  19. package/docs/karnel/npm/index.md +17 -0
  20. package/docs/karnel/osint/index.md +17 -0
  21. package/docs/karnel/plugin/index.md +17 -0
  22. package/docs/karnel/security/index.md +17 -0
  23. package/docs/karnel/shell/index.md +17 -0
  24. package/docs/karnel/ui/index.md +17 -0
  25. package/docs/karnel/utils/index.md +21 -0
  26. package/docs/karnel/voice/index.md +17 -0
  27. package/docs/troubleshooting/index.md +7 -1
  28. package/karnel/RELEASE_COMMIT +1 -1
  29. package/karnel/bin/karnel +1 -1
  30. package/karnel/cli/commands/brain.sh +2 -2
  31. package/karnel/cli/commands/doctor/code_exec.sh +4 -0
  32. package/karnel/cli/commands/doctor/code_langs.sh +1 -1
  33. package/karnel/cli/commands/doctor/fixes.sh +14 -2
  34. package/karnel/cli/commands/doctor.sh +33 -8
  35. package/karnel/cli/commands/open.sh +31 -74
  36. package/karnel/cli/commands/pg.sh +22 -5
  37. package/karnel/cli/commands/show.sh +5 -0
  38. package/karnel/cli/commands/update.sh +3 -3
  39. package/karnel/modules/shell.sh +1 -1
  40. package/karnel/tools/ai/cline/install.sh +9 -1
  41. package/karnel/tools/ai/cursor-cli/install.sh +2 -1
  42. package/karnel/tools/ai/goose/install.sh +1 -1
  43. package/karnel/tools/ai/hermes-agent/install.sh +1 -1
  44. package/karnel/tools/ai/kiro/install.sh +9 -1
  45. package/karnel/tools/deploy/supabase/install.sh +1 -1
  46. package/karnel/tools/plugins/install.sh +17 -3
  47. package/karnel/tools/security/amass/install.sh +1 -1
  48. package/karnel/tools/security/burpsuite/install.sh +1 -1
  49. package/karnel/tools/security/ffuf/install.sh +1 -1
  50. package/karnel/tools/security/gobuster/install.sh +1 -1
  51. package/karnel/tools/security/subfinder/install.sh +1 -1
  52. package/karnel/tools/security/zap/install.sh +1 -1
  53. package/karnel/tools/utils/all.sh +2 -0
  54. package/karnel/tools/utils/herdr/README.md +33 -0
  55. package/karnel/tools/utils/herdr/install.sh +160 -0
  56. package/karnel/tools/utils/zork/zork.sh +8 -3
  57. package/karnel/utils/agent_actions.sh +12 -4
  58. package/karnel/utils/log.sh +1 -1
  59. package/karnel/utils/tools.sh +1 -1
  60. package/karnel/utils/version.sh +1 -1
  61. package/package.json +2 -2
  62. package/scripts/completion.bash +1 -1
  63. package/scripts/completion.zsh +1 -1
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Shell module
3
+ permalink: /karnel/shell/
4
+ layout: base
5
+ ---
6
+
7
+ # Shell module
8
+
9
+ Shell enhancements (ZSH, Oh My Zsh, zoxide).
10
+ ## Install
11
+
12
+ | `shell` | `--powerlevel10k`, `--zsh-defer`, `--zsh-autosuggestions`, `--zsh-syntax-highlighting`, `--history-substring`, `--zsh-completions`, `--fzf-tab`, `--you-should-use`, `--zsh-autopair`, `--better-npm` |
13
+
14
+ See the [CLI reference]({ '/cli/' | relative_url }#shell) for the full command list, or the
15
+ [source on GitHub](https://github.com/israelmarques1024-dotcom/karnel-termux/tree/main/karnel/modules/shell.sh).
16
+
17
+ [← Back to docs]({ '/' | relative_url })
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Ui module
3
+ permalink: /karnel/ui/
4
+ layout: base
5
+ ---
6
+
7
+ # Ui module
8
+
9
+ Termux UI components.
10
+ ## Install
11
+
12
+ | `ui` | `--font`, `--extra-keys`, `--cursor`, `--banner` |
13
+
14
+ See the [CLI reference]({ '/cli/' | relative_url }#ui) for the full command list, or the
15
+ [source on GitHub](https://github.com/israelmarques1024-dotcom/karnel-termux/tree/main/karnel/modules/ui.sh).
16
+
17
+ [← Back to docs]({ '/' | relative_url })
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Utils module
3
+ permalink: /karnel/utils/
4
+ layout: base
5
+ ---
6
+
7
+ # Utils module
8
+
9
+ Utility scripts (converters, notes, QR codes, and more).
10
+ > **🌟 Herdr** — Karnel now ships the [Herdr](https://herdr.dev) terminal AI assistant CLI.
11
+ Install it with `karnel install utils --herdr`. The binary is checksum-verified, installed atomically,
12
+ and tracked with Karnel ownership markers so `karnel uninstall utils --herdr` is clean.
13
+
14
+ ## Install
15
+
16
+ | `utils` | `--fconv`, `--filecheck`, `--websites`, `--notes`, `--treex`, `--passman`, `--applaunch`, `--splash`, `--httptmux`, `--zork`, `--qrcode`, `--superfile`, `--herdr` |
17
+
18
+ See the [CLI reference]({ '/cli/' | relative_url }#utils) for the full command list, or the
19
+ [source on GitHub](https://github.com/israelmarques1024-dotcom/karnel-termux/tree/main/karnel/modules/utils.sh).
20
+
21
+ [← Back to docs]({ '/' | relative_url })
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Voice module
3
+ permalink: /karnel/voice/
4
+ layout: base
5
+ ---
6
+
7
+ # Voice module
8
+
9
+ Voice commands for AI agents.
10
+ ## Install
11
+
12
+ | `voice` | Voice command |
13
+
14
+ See the [CLI reference]({ '/cli/' | relative_url }#voice) for the full command list, or the
15
+ [source on GitHub](https://github.com/israelmarques1024-dotcom/karnel-termux/tree/main/karnel/modules/voice.sh).
16
+
17
+ [← Back to docs]({ '/' | relative_url })
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: Troubleshooting
3
+ permalink: /troubleshooting/
4
+ layout: base
5
+ ---
6
+
1
7
  # Troubleshooting
2
8
 
3
9
  Common issues when installing, updating, or publishing Karnel Termux.
@@ -55,7 +61,7 @@ The workflow runs `actions/jekyll-build-pages` (from `docs/`, output to
55
61
 
56
62
  If a build fails, open the Actions run for the failing job and read the Jekyll
57
63
  output: the usual causes are invalid `_config.yml` YAML, an SCSS compile error
58
- in `assets/css/style.scss`, or a malformed `{{ }}` Liquid tag. After a
64
+ in `assets/css/style.scss`, or a malformed `{% raw %}{{ }}{% endraw %}` Liquid tag. After a
59
65
  successful deploy, the published site is visible under the repository's
60
66
  **Settings → Pages**.
61
67
 
@@ -1 +1 @@
1
- f3ce43f67dd4d213f0af574d1673e8dbd6c910b9
1
+ 3d9d862c0be40df2c3f17648f56bf83ef74507b7
package/karnel/bin/karnel CHANGED
@@ -119,7 +119,7 @@ show_update_notification() {
119
119
  fi
120
120
  }
121
121
 
122
- if [[ "${KARNEL_READ_ONLY:-0}" != "1" ]] && [[ "${1:-}" != "update" ]] && [[ "${1:-}" != "uninstall" ]] && [[ "${1:-}" != "reinstall" ]]; then
122
+ if [[ "${KARNEL_READ_ONLY:-0}" != "1" ]] && [[ "${1:-}" != "update" ]] && [[ "${1:-}" != "uninstall" ]] && [[ "${1:-}" != "reinstall" ]] && [[ "${1:-}" != "upgrade" ]]; then
123
123
  check_for_updates
124
124
  import "@/utils/log"
125
125
  import "@/utils/colors"
@@ -121,7 +121,7 @@ _brain_slug_escape() {
121
121
  _brain_update_related() {
122
122
  local file="$1" slug="$2"
123
123
  local tmp
124
- tmp=$(mktemp "${TMPDIR:-/tmp}/karnel-brain.XXXXXX")
124
+ tmp=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-brain.XXXXXX")
125
125
  local frontmatter_end
126
126
  frontmatter_end=$(awk '/^---$/ {++n} n==2 {print NR; exit}' "$file")
127
127
 
@@ -151,7 +151,7 @@ _brain_update_related() {
151
151
  _brain_remove_related() {
152
152
  local file="$1" slug="$2"
153
153
  local tmp
154
- tmp=$(mktemp "${TMPDIR:-/tmp}/karnel-brain.XXXXXX")
154
+ tmp=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-brain.XXXXXX")
155
155
  local frontmatter_end
156
156
  frontmatter_end=$(awk '/^---$/ {++n} n==2 {print NR; exit}' "$file")
157
157
 
@@ -126,6 +126,10 @@ _exec_apply_fix() {
126
126
  fi
127
127
 
128
128
  log_info "Applying fix: $tool ($lang)..."
129
+ # Snapshot the target before an in-place rewrite so the user can revert.
130
+ if [[ -n "$file" && -f "$file" && ! -e "$file.karnel-bak" ]]; then
131
+ cp -p "$file" "$file.karnel-bak" 2>/dev/null || true
132
+ fi
129
133
  local out rc
130
134
  if out=$(cd "$dir" && timeout 60 bash -c "$full_cmd" 2>&1); then
131
135
  rc=0
@@ -64,7 +64,7 @@ _init_lang_tools() {
64
64
  LANG_TOOLS["C/C++:clang_tidy"]="clang-tidy|lint|clang-tidy {} -- -std=c17 2>&1 | head -20|||official"
65
65
 
66
66
  # Java
67
- LANG_TOOLS["Java:javac"]="javac|syntax|javac -d \"${TMPDIR:-/tmp}\" -Xlint:all {} 2>&1 | head -10|||official"
67
+ LANG_TOOLS["Java:javac"]="javac|syntax|javac -d \"${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}\" -Xlint:all {} 2>&1 | head -10|||official"
68
68
 
69
69
  # Docker
70
70
  LANG_TOOLS["Docker:hadolint"]="hadolint|lint|hadolint Dockerfile|||community"
@@ -3,6 +3,7 @@
3
3
 
4
4
  import "@/utils/log"
5
5
  import "@/utils/colors"
6
+ import "@/utils/install"
6
7
 
7
8
  _fix_storage() {
8
9
  termux-setup-storage 2>/dev/null
@@ -207,7 +208,7 @@ _fix_psutil() {
207
208
  local tmp="$PREFIX/tmp/psutil_patch"
208
209
  mkdir -p "$tmp"
209
210
  pip download psutil==7.2.2 --no-binary :all: --no-deps -d "$tmp" 2>/dev/null || return 1
210
- tar xzf "$tmp/psutil-7.2.2.tar.gz" -C "$tmp"
211
+ safe_extract_tar "$tmp/psutil-7.2.2.tar.gz" "$tmp" || return 1
211
212
  if grep -q 'LINUX = sys.platform.startswith("linux")' "$tmp/psutil-7.2.2/psutil/_common.py" 2>/dev/null; then
212
213
  sed -i 's/LINUX = sys.platform.startswith("linux")/LINUX = sys.platform.startswith(("linux", "android"))/' "$tmp/psutil-7.2.2/psutil/_common.py"
213
214
  else
@@ -248,7 +249,18 @@ _fix_npm_shebangs() {
248
249
  }
249
250
 
250
251
  _fix_broken_symlinks() {
251
- find "$PREFIX/bin" -type l ! -exec test -e {} \; -delete 2>/dev/null
252
+ local link target
253
+ for link in "$PREFIX/bin"/*; do
254
+ [[ -L "$link" ]] || continue
255
+ [[ -e "$link" ]] && continue
256
+ target="$(readlink -f "$link" 2>/dev/null || true)"
257
+ # Only remove broken symlinks that point into Karnel-managed locations so we
258
+ # never delete unrelated third-party software from the user's environment.
259
+ if [[ "$target" == "$KARNEL_PATH"/* || "$target" == "$KARNEL_DATA"/* ||
260
+ "$target" == "$PREFIX/lib/node_modules"/* || "$target" == "$PREFIX/lib"/* ]]; then
261
+ rm -f "$link" 2>/dev/null || true
262
+ fi
263
+ done
252
264
  return 0
253
265
  }
254
266
 
@@ -1413,20 +1413,45 @@ doctor_termux() {
1413
1413
  fi
1414
1414
  if [[ "$confirm" == "y" ]]; then
1415
1415
  echo
1416
+ local -A DESTRUCTIVE_FIXES=(
1417
+ [_fix_ai_install]=1
1418
+ [_fix_proot_ubuntu]=1
1419
+ [_fix_disk_cleanup]=1
1420
+ [_fix_broken_symlinks]=1
1421
+ [_fix_apt_cache]=1
1422
+ )
1416
1423
  for ((i=0; i<${#fix_commands[@]}; i++)); do
1417
1424
  log_info "Fixing: ${fix_descriptions[$i]}..."
1418
1425
  local callback="${fix_callbacks[$i]:-}"
1419
1426
  local success=false
1420
-
1421
- if [[ -n "$callback" ]] && type "$callback" &>/dev/null 2>&1; then
1422
- "$callback" && success=true
1427
+ local apply_this="y"
1428
+
1429
+ # Destructive fixes are never auto-applied in --fix mode. In an
1430
+ # interactive --fix session they require an explicit confirmation; in a
1431
+ # non-interactive one they are skipped (fail-closed) to avoid wiping
1432
+ # caches, reinstalling packages or deleting unrelated software.
1433
+ if [[ -n "$callback" && -n "${DESTRUCTIVE_FIXES[$callback]:-}" ]]; then
1434
+ if $FIX_MODE; then
1435
+ if [[ -t 0 ]]; then
1436
+ read_confirm "DANGEROUS: ${fix_descriptions[$i]}. Apply?" apply_this
1437
+ else
1438
+ log_warn "Skipping destructive fix (non-interactive): ${fix_descriptions[$i]}"
1439
+ continue
1440
+ fi
1441
+ fi
1423
1442
  fi
1424
1443
 
1425
- if [[ "$success" == "true" ]]; then
1426
- ((fixed++))
1427
- log_success "Fixed: ${fix_descriptions[$i]}"
1428
- else
1429
- log_warn "Could not fix: ${fix_descriptions[$i]}"
1444
+ if [[ "$apply_this" == "y" ]]; then
1445
+ if [[ -n "$callback" ]] && type "$callback" &>/dev/null 2>&1; then
1446
+ "$callback" && success=true
1447
+ fi
1448
+
1449
+ if [[ "$success" == "true" ]]; then
1450
+ ((fixed++))
1451
+ log_success "Fixed: ${fix_descriptions[$i]}"
1452
+ else
1453
+ log_warn "Could not fix: ${fix_descriptions[$i]}"
1454
+ fi
1430
1455
  fi
1431
1456
  done
1432
1457
  echo
@@ -3,7 +3,7 @@
3
3
  import "@/utils/log"
4
4
  import "@/utils/colors"
5
5
 
6
- OPEN_BASE_URL="https://karneltermux.vercel.app"
6
+ OPEN_DOCS="https://israelmarques1024-dotcom.github.io/karnel-termux"
7
7
 
8
8
  open_main() {
9
9
  if [[ $# -eq 0 ]]; then
@@ -16,74 +16,30 @@ open_main() {
16
16
 
17
17
  case "$target" in
18
18
  karnel | help)
19
- url="$OPEN_BASE_URL/karnel"
19
+ url="$OPEN_DOCS/"
20
20
  ;;
21
- lang)
22
- url="$OPEN_BASE_URL/karnel/lang"
23
- ;;
24
- db)
25
- url="$OPEN_BASE_URL/karnel/db"
26
- ;;
27
- ai)
28
- url="$OPEN_BASE_URL/karnel/ai"
29
- ;;
30
- editor)
31
- url="$OPEN_BASE_URL/karnel/editor"
32
- ;;
33
- dev)
34
- url="$OPEN_BASE_URL/karnel/dev"
35
- ;;
36
- npm)
37
- url="$OPEN_BASE_URL/karnel/npm"
38
- ;;
39
- shell)
40
- url="$OPEN_BASE_URL/karnel/shell"
41
- ;;
42
- ui)
43
- url="$OPEN_BASE_URL/karnel/ui"
44
- ;;
45
- auto)
46
- url="$OPEN_BASE_URL/karnel/auto"
47
- ;;
48
- deploy)
49
- url="$OPEN_BASE_URL/karnel/deploy"
21
+ herdr)
22
+ url="$OPEN_DOCS/karnel/utils"
50
23
  ;;
51
24
  supabase)
52
- url="$OPEN_BASE_URL/karnel/supabase"
25
+ url="$OPEN_DOCS/karnel/deploy"
26
+ ;;
27
+ cleanup)
28
+ url="$OPEN_DOCS/cli"
53
29
  ;;
54
- cleanup)
55
- url="$OPEN_BASE_URL/karnel/cleanup"
56
- ;;
57
- network)
58
- url="$OPEN_BASE_URL/karnel/network"
59
- ;;
60
- utils)
61
- url="$OPEN_BASE_URL/karnel/utils"
62
- ;;
63
- games)
64
- url="$OPEN_BASE_URL/karnel/games"
65
- ;;
66
- voice)
67
- url="$OPEN_BASE_URL/karnel/voice"
68
- ;;
69
- plugin)
70
- url="$OPEN_BASE_URL/karnel/plugin"
71
- ;;
72
- security)
73
- url="$OPEN_BASE_URL/karnel/security"
74
- ;;
75
- osint|robin)
76
- url="$OPEN_BASE_URL/karnel/osint"
77
- ;;
78
- --help | -h)
30
+ --help | -h)
79
31
  open_help
80
32
  return
81
33
  ;;
82
34
  *)
83
- log_error "Unknown target: $target"
84
- echo
85
- open_help
86
- return 1
35
+ if [[ -f "$KARNEL_PATH/modules/$target.sh" ]]; then
36
+ url="$OPEN_DOCS/karnel/$target"
37
+ else
38
+ log_error "Unknown target: $target"
39
+ echo
40
+ open_help
41
+ return 1
42
+ fi
87
43
  ;;
88
44
  esac
89
45
 
@@ -130,20 +86,21 @@ open_help() {
130
86
  printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "npm" "Node.js tools"
131
87
  printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "shell" "ZSH shell"
132
88
  printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "ui" "Termux UI"
133
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "auto" "Automation tools"
134
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "deploy" "Deploy CLIs"
135
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "supabase" "Supabase CLI"
136
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "games" "Games"
137
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "cleanup" "Cache cleanup"
138
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "network" "Network tools"
139
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "utils" "Utility tools"
140
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "voice" "Voice command"
141
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "osint" "OSINT tools"
142
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "plugin" "Plugin system"
143
- printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "security" "Security tools"
144
- echo
89
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "auto" "Automation tools"
90
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "deploy" "Deploy CLIs"
91
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "supabase" "Supabase CLI"
92
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "games" "Games"
93
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "cleanup" "Cache cleanup"
94
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "network" "Network tools"
95
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "utils" "Utility tools"
96
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "voice" "Voice command"
97
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "osint" "OSINT tools"
98
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "plugin" "Plugin system"
99
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "security" "Security tools"
100
+ printf " ${D_GREEN}%-14s${NC} ${D_DIM}%s${NC}\n" "herdr" "Herdr terminal AI assistant"
101
+ echo
145
102
  separator_section "Website"
146
103
  echo
147
- list_item "${D_CYAN}$OPEN_BASE_URL${NC}"
104
+ list_item "${D_CYAN}$OPEN_DOCS${NC}"
148
105
  echo
149
106
  }
@@ -635,8 +635,11 @@ pg_restore_db() {
635
635
  success=true
636
636
  fi
637
637
  else
638
- if loading "Restoring database" pg_restore -d "$db_name" -c "$backup_file" 2>/dev/null || pg_restore -d "$db_name" "$backup_file" 2>/dev/null; then
638
+ local pg_log="$KARNEL_CACHE/pg-restore.log"
639
+ if loading "Restoring database" pg_restore -d "$db_name" -c --single-transaction "$backup_file" 2>>"$pg_log"; then
639
640
  success=true
641
+ else
642
+ log_warn "pg_restore failed; details in: $pg_log"
640
643
  fi
641
644
  fi
642
645
 
@@ -736,10 +739,24 @@ pg_schedule() {
736
739
 
737
740
  local escaped_db; printf -v escaped_db '%q' "$db_name"
738
741
  local escaped_path; printf -v escaped_path '%q' "$KARNEL_PATH"
739
- local job="$cron_expr KARNEL_PATH=$escaped_path $escaped_path/bin/karnel pg backup $escaped_db >/dev/null 2>&1"
740
- (crontab -l 2>/dev/null | grep -v "karnel pg backup $db_name"; echo "$job") | crontab -
741
- log_success "Backup scheduled successfully ($interval) for database '$db_name'!"
742
- list_item "Ensure cron daemon is running (run: 'crond')"
742
+ local pg_log="$KARNEL_CACHE/pg-backup.log"
743
+ local job="$cron_expr PATH=\"$PREFIX/bin:/usr/bin:/bin\" PREFIX=\"$PREFIX\" KARNEL_PATH=$escaped_path $escaped_path/bin/karnel pg backup $escaped_db >>\"$pg_log\" 2>&1"
744
+ local tmpcron
745
+ tmpcron="$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-pg.XXXXXX")" || {
746
+ log_error "Failed to schedule backup for database '$db_name'"
747
+ return 1
748
+ }
749
+ {
750
+ crontab -l 2>/dev/null | grep -vF "karnel pg backup $escaped_db"
751
+ echo "$job"
752
+ } >"$tmpcron"
753
+ if crontab "$tmpcron"; then
754
+ log_success "Backup scheduled successfully ($interval) for database '$db_name'!"
755
+ list_item "Ensure cron daemon is running (run: 'crond')"
756
+ else
757
+ log_error "Failed to schedule backup for database '$db_name'"
758
+ fi
759
+ rm -f "$tmpcron"
743
760
  fi
744
761
  echo
745
762
  }
@@ -47,6 +47,11 @@ show_main() {
47
47
  return 1
48
48
  fi
49
49
 
50
+ if [[ ! "$module" =~ ^[A-Za-z0-9_-]+$ ]]; then
51
+ log_error "Invalid module name: $module"
52
+ return 1
53
+ fi
54
+
50
55
  if [[ "$module" == "backup" ]]; then
51
56
  _show_backup_docs "$tool"
52
57
  return
@@ -226,9 +226,9 @@ _update_try_curl() {
226
226
  command -v curl &>/dev/null || return 1
227
227
 
228
228
  local meta installer sumfile tag
229
- meta=$(mktemp "${TMPDIR:-/tmp}/karnel-meta.XXXXXX") || return 1
230
- installer=$(mktemp "${TMPDIR:-/tmp}/karnel-install.XXXXXX") || { rm -f "$meta"; return 1; }
231
- sumfile=$(mktemp "${TMPDIR:-/tmp}/karnel-sum.XXXXXX") || { rm -f "$meta" "$installer"; return 1; }
229
+ meta=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-meta.XXXXXX") || return 1
230
+ installer=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-install.XXXXXX") || { rm -f "$meta"; return 1; }
231
+ sumfile=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-sum.XXXXXX") || { rm -f "$meta" "$installer"; return 1; }
232
232
 
233
233
  log_info "Trying the official curl installer..."
234
234
 
@@ -32,7 +32,7 @@ install_oh_my_zsh() (
32
32
  echo
33
33
 
34
34
  local temp_base temp_dir temp_file
35
- temp_base="${TMPDIR:-${PREFIX:-/tmp}/tmp}"
35
+ temp_base="${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}"
36
36
  mkdir -p "$temp_base" || return 1
37
37
  temp_dir=$(mktemp -d "$temp_base/karnel-omz.XXXXXX") || {
38
38
  log_error "Failed to create private Oh My Zsh temporary directory"
@@ -3,6 +3,14 @@
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
5
  import "@/utils/colors"
6
+ import "@/utils/install"
7
+
8
+ if ! declare -f safe_extract_tar >/dev/null 2>&1; then
9
+ safe_extract_tar() {
10
+ local archive="$1" outdir="$2" strip_components="${3:-0}"
11
+ tar -xzf "$archive" -C "$outdir" --strip-components="$strip_components"
12
+ }
13
+ fi
6
14
 
7
15
  : "${KARNEL_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
8
16
  : "${KARNEL_CACHE:=${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
@@ -99,7 +107,7 @@ _cline_install_global_impl() {
99
107
 
100
108
  mkdir -p "$CLINE_DATA_DIR"
101
109
  : >"$CLINE_DATA_DIR/$CLINE_MARKER"
102
- if ! tar -xzf "$tarball" -C "$CLINE_DATA_DIR" &>>"$LOG_FILE"; then
110
+ if ! safe_extract_tar "$tarball" "$CLINE_DATA_DIR" &>>"$LOG_FILE"; then
103
111
  rm -f "$tarball"
104
112
  log_error "Failed to extract cline linux-arm64 binary"
105
113
  return 1
@@ -3,6 +3,7 @@
3
3
  import "@/utils/log"
4
4
  import "@/utils/colors"
5
5
  import "@/utils/version"
6
+ import "@/utils/install"
6
7
 
7
8
  : "${KARNEL_CACHE:=${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
8
9
  : "${KARNEL_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
@@ -142,7 +143,7 @@ _download_cursor_binary_impl() {
142
143
  return 1
143
144
  fi
144
145
 
145
- if ! tar -zxf "$staging_dir/$tarball" --strip-components=1 -C "$staging_dir" &>>"$LOG_FILE"; then
146
+ if ! safe_extract_tar "$staging_dir/$tarball" "$staging_dir" 1; then
146
147
  rm -rf "$staging_dir"
147
148
  log_error "Failed to extract Cursor CLI binary"
148
149
  return 1
@@ -197,7 +197,7 @@ _goose_download_binary_proot_impl() {
197
197
  return 1
198
198
  fi
199
199
  verify_github_release_asset aaif-goose/goose "v${latest_version}" "$tarball" "$GOOSE_DATA_DIR/$tarball" || return 1
200
- if ! tar -xzf "$GOOSE_DATA_DIR/$tarball" -C "$GOOSE_DATA_DIR" &>>"$LOG_FILE"; then
200
+ if ! extract_tarball "$GOOSE_DATA_DIR/$tarball" "$GOOSE_DATA_DIR" &>>"$LOG_FILE"; then
201
201
  rm -f "$GOOSE_DATA_DIR/$tarball"
202
202
  log_error "Failed to extract Goose CLI"
203
203
  return 1
@@ -41,7 +41,7 @@ _patch_psutil_for_termux() {
41
41
  local tmp
42
42
  tmp=$(mktemp -d "$KARNEL_CACHE/psutil_patch.XXXXXX") || return 1
43
43
  "$HERMES_VENV/bin/python" -m pip download psutil==7.2.2 --no-binary :all: --no-deps -d "$tmp" 2>/dev/null || { rm -rf "$tmp"; return 1; }
44
- tar xzf "$tmp/psutil-7.2.2.tar.gz" -C "$tmp" || { rm -rf "$tmp"; return 1; }
44
+ safe_extract_tar "$tmp/psutil-7.2.2.tar.gz" "$tmp" || { rm -rf "$tmp"; return 1; }
45
45
  sed -i 's/LINUX = sys.platform.startswith("linux")/LINUX = sys.platform.startswith(("linux", "android"))/' "$tmp/psutil-7.2.2/psutil/_common.py" || { rm -rf "$tmp"; return 1; }
46
46
  "$HERMES_VENV/bin/python" -m pip install "$tmp/psutil-7.2.2" || { rm -rf "$tmp"; return 1; }
47
47
  rm -rf "$tmp"
@@ -3,6 +3,14 @@
3
3
  import "@/utils/log"
4
4
  import "@/utils/colors"
5
5
  import "@/utils/version"
6
+ import "@/utils/install"
7
+
8
+ if ! declare -f safe_extract_tar >/dev/null 2>&1; then
9
+ safe_extract_tar() {
10
+ local archive="$1" outdir="$2" strip_components="${3:-0}"
11
+ tar -xzf "$archive" -C "$outdir" --strip-components="$strip_components"
12
+ }
13
+ fi
6
14
 
7
15
  : "${KARNEL_CACHE:=${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
8
16
  : "${KARNEL_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
@@ -136,7 +144,7 @@ print(version, download, checksum, sep="\t")
136
144
  fi
137
145
  log_success "Checksum verified"
138
146
 
139
- if tar xzf "$archive" -C "$tmpdir" 2>>"$LOG_FILE"; then
147
+ if safe_extract_tar "$archive" "$tmpdir" 2>>"$LOG_FILE"; then
140
148
  bin_path=$(find "$tmpdir" -name "kiro-cli" -type f -executable 2>/dev/null | head -1)
141
149
  if [[ -z "$bin_path" ]]; then
142
150
  bin_path=$(find "$tmpdir" -name "kiro*" -type f -executable 2>/dev/null | head -1)
@@ -52,7 +52,7 @@ install_supabase() (
52
52
  local checksum_url="${_SUPABASE_RELEASE_URL}/supabase_${_SUPABASE_VERSION}_checksums.txt"
53
53
  local tmp_dir staged_bin marker_staging="" old_bin="" old_marker=""
54
54
  local _kdir="${KARNEL_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
55
- mkdir -p "$_kdir" 2>/dev/null || _kdir="${TMPDIR:-/tmp}"
55
+ mkdir -p "$_kdir" 2>/dev/null || _kdir="${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}"
56
56
  tmp_dir=$(mktemp -d "$_kdir/supabase.XXXXXX") || return 1
57
57
  staged_bin=""
58
58
  trap 'rm -rf "$tmp_dir"; [[ -z "$staged_bin" ]] || rm -f "$staged_bin"; [[ -z "$marker_staging" ]] || rm -f "$marker_staging"' EXIT
@@ -519,7 +519,10 @@ _plugin_verify_checksum() {
519
519
  local expected actual
520
520
 
521
521
  expected="$(jq -r '.checksum // empty' "$manifest")" || return 1
522
- [[ -n "$expected" ]] || return 0
522
+ if [[ -z "$expected" ]]; then
523
+ log_error "Plugin '$(jq -r '.name' "$manifest")' has no checksum; refusing install"
524
+ return 1
525
+ fi
523
526
 
524
527
  actual="$(_plugin_payload_checksum "$plugin_root")" || return 1
525
528
  if [[ "$actual" != "$expected" ]]; then
@@ -882,11 +885,15 @@ _plugin_clone_repository() {
882
885
  local repo="$1"
883
886
  local ref="$2"
884
887
  local destination="$3"
888
+ local commit="$4"
885
889
  local source_url="https://github.com/$repo.git"
886
890
 
887
891
  _plugin_require_command git || return 1
888
892
 
889
- if [[ -n "$ref" ]]; then
893
+ if [[ -n "$commit" ]]; then
894
+ git clone "$source_url" "$destination" || return 1
895
+ git -C "$destination" checkout "$commit" --detach || return 1
896
+ elif [[ -n "$ref" ]]; then
890
897
  git clone --depth=1 --branch "$ref" "$source_url" "$destination"
891
898
  else
892
899
  git clone --depth=1 "$source_url" "$destination"
@@ -1083,6 +1090,13 @@ _plugin_stage_and_install() {
1083
1090
  local replace_existing="$7"
1084
1091
  local staging checkout checkout_root candidate candidate_root manifest_name commit installed_ref expected_commit registry_name
1085
1092
 
1093
+ expected_commit=""
1094
+ registry_name=""
1095
+ if [[ -n "$registry_entry" ]]; then
1096
+ expected_commit="$(jq -r '.commit // empty' <<<"$registry_entry")" || return 1
1097
+ registry_name="$(jq -r '.name' <<<"$registry_entry")" || return 1
1098
+ fi
1099
+
1086
1100
  _plugin_prepare_plugins_dir || return 1
1087
1101
  _plugin_validate_repo "$repo" || return 1
1088
1102
  [[ -z "$ref" ]] || _plugin_ref_is_valid "$ref" || {
@@ -1100,7 +1114,7 @@ _plugin_stage_and_install() {
1100
1114
  }
1101
1115
  checkout="$staging/repository"
1102
1116
 
1103
- if ! _plugin_clone_repository "$repo" "$ref" "$checkout"; then
1117
+ if ! _plugin_clone_repository "$repo" "$ref" "$checkout" "$expected_commit"; then
1104
1118
  log_error "Failed to clone plugin repository '$repo'."
1105
1119
  _plugin_cleanup_staging "$staging"
1106
1120
  return 1
@@ -47,7 +47,7 @@ install_amass() (
47
47
  esac
48
48
  url="https://github.com/owasp-amass/amass/releases/download/v${_AMASS_VERSION}/$asset"
49
49
 
50
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/amass.XXXXXX") || return 1
50
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/amass.XXXXXX") || return 1
51
51
  staged_bin=""
52
52
  trap 'rm -rf "$tmpdir"; [ -z "$staged_bin" ] || rm -f "$staged_bin"' EXIT
53
53
  archive="$tmpdir/amass.zip"
@@ -28,7 +28,7 @@ install_burpsuite() (
28
28
 
29
29
  local url tmpdir staged_dir wrapper old_dir wrapper_backup
30
30
  url="https://portswigger.net/burp/releases/download?product=community&type=Jar&version=${_BURP_VERSION}"
31
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/burpsuite.XXXXXX") || return 1
31
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/burpsuite.XXXXXX") || return 1
32
32
  trap 'rm -rf "$tmpdir"' EXIT
33
33
  staged_dir="$tmpdir/data"
34
34
  mkdir -p "$staged_dir" "$PREFIX/bin" "$(dirname "$_BURP_DIR")" || return 1
@@ -26,7 +26,7 @@ _download_ffuf() (
26
26
  *) return 1 ;;
27
27
  esac
28
28
  url="https://github.com/ffuf/ffuf/releases/download/v${_FFUF_VERSION}/$asset"
29
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/ffuf.XXXXXX") || return 1
29
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/ffuf.XXXXXX") || return 1
30
30
  staged_bin=""
31
31
  trap 'rm -rf "$tmpdir"; [ -z "$staged_bin" ] || rm -f "$staged_bin"' EXIT
32
32
  archive="$tmpdir/ffuf.tar.gz"