karnel-termux 4.17.22 → 4.17.27

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 (39) hide show
  1. package/README.md +3 -2
  2. package/install.sh +20 -5
  3. package/karnel/RELEASE_COMMIT +1 -1
  4. package/karnel/cli/commands/doctor/fixes.sh +4 -4
  5. package/karnel/cli/commands/doctor.sh +4 -2
  6. package/karnel/cli/commands/update.sh +18 -0
  7. package/karnel/cli/commands/upgrade.sh +9 -2
  8. package/karnel/modules/voice.sh +8 -5
  9. package/karnel/tools/ai/codebuff/install.sh +69 -9
  10. package/karnel/tools/ai/codex/install.sh +3 -0
  11. package/karnel/tools/ai/gemini-cli/install.sh +3 -0
  12. package/karnel/tools/ai/kimi-code/install.sh +3 -0
  13. package/karnel/tools/ai/minimax-cli/install.sh +3 -0
  14. package/karnel/tools/ai/openclaude/install.sh +3 -0
  15. package/karnel/tools/ai/openclaw/install.sh +3 -0
  16. package/karnel/tools/ai/pi/install.sh +3 -0
  17. package/karnel/tools/ai/qoder/install.sh +140 -29
  18. package/karnel/tools/ai/qwen-code/install.sh +3 -0
  19. package/karnel/tools/deploy/netlify/install.sh +10 -3
  20. package/karnel/tools/deploy/vercel/install.sh +10 -3
  21. package/karnel/tools/npm/all.sh +1 -23
  22. package/karnel/tools/npm/live-server/install.sh +2 -0
  23. package/karnel/tools/npm/localtunnel/install.sh +3 -1
  24. package/karnel/tools/npm/markserv/install.sh +2 -0
  25. package/karnel/tools/npm/ncu/install.sh +2 -0
  26. package/karnel/tools/npm/nestjs/install.sh +2 -0
  27. package/karnel/tools/npm/ngrok/install.sh +2 -0
  28. package/karnel/tools/npm/prettier/install.sh +2 -0
  29. package/karnel/tools/npm/psqlformat/install.sh +2 -0
  30. package/karnel/tools/npm/typescript/install.sh +2 -0
  31. package/karnel/tools/npm/vercel/install.sh +2 -0
  32. package/karnel/tools/utils/filecheck/filecheck.py +1 -0
  33. package/karnel/tools/utils/qrcode/install.sh +15 -1
  34. package/karnel/tools/utils/qrcode/qrcode.py +7 -28
  35. package/karnel/tools/utils/websites/websites.py +1 -0
  36. package/karnel/tools/voice/uninstall.sh +1 -0
  37. package/karnel/utils/downloaded-python.sh +1 -1
  38. package/karnel/utils/npm-shebang.sh +30 -0
  39. package/package.json +1 -1
package/README.md CHANGED
@@ -636,8 +636,9 @@ karnel update karnel # Update the framework
636
636
 
637
637
  `karnel update` requires a target such as `karnel`, `ai`, or `security`.
638
638
  `karnel update karnel` first runs the official curl installer, then falls back to
639
- the local Git checkout and package-manager installs if needed. The curl installer
640
- is pinned to the latest GitHub release tag, and its SHA-256 checksum is verified
639
+ the local Git checkout if needed. Package-manager fallback is used only when the
640
+ active framework was installed by that package manager. The curl installer is
641
+ pinned to the latest GitHub release tag, and its SHA-256 checksum is verified
641
642
  before it runs. The framework checks for a new version in the background at most
642
643
  once every 24 hours; the check can use npm or GitHub and writes state under
643
644
  `$KARNEL_CACHE`.
package/install.sh CHANGED
@@ -147,7 +147,10 @@ bootstrap_dependencies() {
147
147
 
148
148
  if [[ $needed_tput -eq 1 ]]; then
149
149
  echo -e " ${P_BORDER}→${P_NC} Installing ncurses-utils..."
150
- pkg install -y ncurses-utils &>/dev/null || true
150
+ if ! pkg install -y ncurses-utils &>/dev/null; then
151
+ log_fail "Failed to install ncurses-utils"
152
+ return 1
153
+ fi
151
154
  echo -e " ${P_OK}✔${P_NC} ncurses installed"
152
155
  echo
153
156
  fi
@@ -155,7 +158,10 @@ bootstrap_dependencies() {
155
158
  if [[ $needed_git -eq 1 ]]; then
156
159
  log_info "Installing git..."
157
160
  progress_bar 0 10
158
- pkg install -y git &>/dev/null || true
161
+ if ! pkg install -y git &>/dev/null; then
162
+ log_fail "Failed to install git"
163
+ return 1
164
+ fi
159
165
  progress_bar 10 10
160
166
  echo
161
167
  log_ok "git installed"
@@ -164,7 +170,10 @@ bootstrap_dependencies() {
164
170
  if [[ $needed_glow -eq 1 ]]; then
165
171
  log_info "Installing glow..."
166
172
  progress_bar 0 10
167
- pkg install -y glow &>/dev/null || true
173
+ if ! pkg install -y glow &>/dev/null; then
174
+ log_fail "Failed to install glow"
175
+ return 1
176
+ fi
168
177
  progress_bar 10 10
169
178
  echo
170
179
  log_ok "glow installed"
@@ -173,7 +182,10 @@ bootstrap_dependencies() {
173
182
  if [[ $needed_gh -eq 1 ]]; then
174
183
  log_info "Installing gh (GitHub CLI)..."
175
184
  progress_bar 0 10
176
- pkg install -y gh &>/dev/null || true
185
+ if ! pkg install -y gh &>/dev/null; then
186
+ log_fail "Failed to install gh"
187
+ return 1
188
+ fi
177
189
  progress_bar 10 10
178
190
  echo
179
191
  log_ok "gh installed"
@@ -182,7 +194,10 @@ bootstrap_dependencies() {
182
194
  if [[ $needed_rg -eq 1 ]]; then
183
195
  log_info "Installing ripgrep..."
184
196
  progress_bar 0 10
185
- pkg install -y ripgrep &>/dev/null || true
197
+ if ! pkg install -y ripgrep &>/dev/null; then
198
+ log_fail "Failed to install ripgrep"
199
+ return 1
200
+ fi
186
201
  progress_bar 10 10
187
202
  echo
188
203
  log_ok "ripgrep installed"
@@ -1 +1 @@
1
- 4c9e5da4c38f3c93a5f36809265feb152fd59b27
1
+ da9b390f62c1b73f31fa5a00a458162589b09c38
@@ -342,10 +342,11 @@ _fix_script_symlinks() {
342
342
  local tool_name
343
343
  tool_name=$(basename "$tool_dir")
344
344
  local bin_names
345
- bin_names=$(grep -E '^local BIN_NAME=' "$installer" 2>/dev/null | head -1 | sed "s/.*BIN_NAME=\"\?//;s/\"\?$//")
345
+ bin_names=$(grep -E '^[[:space:]]*local BIN_NAME=' "$installer" 2>/dev/null | head -1 | sed "s/.*BIN_NAME=\"\?//;s/\"\?$//")
346
346
  [[ -z "$bin_names" ]] && bin_names="$tool_name"
347
347
  for bin_name in $bin_names; do
348
348
  local expected="$tool_dir/$bin_name.py"
349
+ [[ -f "$expected" ]] || continue
349
350
  local link="$PREFIX/bin/$bin_name"
350
351
  if [[ -L "$link" ]]; then
351
352
  local target
@@ -403,15 +404,14 @@ _fix_script_missing() {
403
404
  local module_name
404
405
  module_name=$(basename "$module_dir")
405
406
  local bin_names
406
- bin_names=$(grep -E '^local BIN_NAME=' "$installer" 2>/dev/null | head -1 | sed "s/.*BIN_NAME=\"\?//;s/\"\?$//")
407
+ bin_names=$(grep -E '^[[:space:]]*local BIN_NAME=' "$installer" 2>/dev/null | head -1 | sed "s/.*BIN_NAME=\"\?//;s/\"\?$//")
407
408
  [[ -z "$bin_names" ]] && bin_names="$tool_name"
408
409
  for bin_name in $bin_names; do
409
410
  ((total++))
410
411
  if ! command -v "$bin_name" &>/dev/null; then
411
412
  karnel install "$module_name" "--$tool_name" &>/dev/null && ((fixed++))
412
413
  fi
413
- done
414
- done < <(find "$module_dir" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null || true)
414
+ done done < <(find "$module_dir" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null || true)
415
415
  done < <(find "$KARNEL_PATH/tools/" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null || true)
416
416
  [[ $fixed -gt 0 ]] && log_success "Reinstalled $fixed/$total missing tool(s)"
417
417
  return 0
@@ -671,7 +671,7 @@ doctor_termux() {
671
671
  [[ -f "$installer" ]] || continue
672
672
 
673
673
  local bin_names
674
- bin_names=$(grep -E '^local BIN_NAME=' "$installer" 2>/dev/null | head -1 | sed "s/.*BIN_NAME=\"\?//;s/\"\?$//")
674
+ bin_names=$(grep -E '^[[:space:]]*local BIN_NAME=' "$installer" 2>/dev/null | head -1 | sed "s/.*BIN_NAME=\"\?//;s/\"\?$//")
675
675
  [[ -z "$bin_names" ]] && bin_names="$tool_name"
676
676
 
677
677
  for bin_name in $bin_names; do
@@ -684,12 +684,14 @@ doctor_termux() {
684
684
  if [[ "$target" == "$tool_script" ]]; then
685
685
  log_success "$module_name/$tool_name → $bin_name (OK)"
686
686
  ((script_ok++))
687
+ elif [[ ! -f "$tool_script" ]]; then
688
+ log_info "$bin_name: managed outside Karnel python payloads (→ $target)"
687
689
  else
688
690
  log_warn "$bin_name: symlink target changed (→ $target, expected $tool_script)"
689
691
  ((script_warn++)); found_symlink_issues=true
690
692
  fi
691
693
 
692
- if [[ -f "$bin_path" ]] && head -1 "$bin_path" 2>/dev/null | grep -q "^#!/usr/bin/env"; then
694
+ if [[ -f "$tool_script" ]] && [[ -f "$bin_path" ]] && head -1 "$bin_path" 2>/dev/null | grep -q "^#!/usr/bin/env"; then
693
695
  log_warn "$bin_name: uses #!/usr/bin/env — will fail on Termux"
694
696
  ((script_warn++)); found_shebang_issues=true
695
697
  fi
@@ -357,8 +357,18 @@ _npm_installed_version() {
357
357
  printf '%s' "${v:-unknown}"
358
358
  }
359
359
 
360
+ _update_global_package_is_active() {
361
+ local manager="${1:-npm}" root
362
+ root=$("$manager" root -g 2>/dev/null) || return 1
363
+ [[ "$KARNEL_PATH" == "$root/karnel-termux/karnel" ]]
364
+ }
365
+
360
366
  _update_try_npm() {
361
367
  command -v npm &>/dev/null || return 1
368
+ if ! _update_global_package_is_active; then
369
+ log_info "Skipping npm update: the active Karnel framework is not npm-managed"
370
+ return 1
371
+ fi
362
372
  log_info "Trying npm update..."
363
373
  if npm update -g karnel-termux --ignore-scripts 2>/dev/null; then
364
374
  local new_ver
@@ -372,6 +382,10 @@ _update_try_npm() {
372
382
 
373
383
  _update_try_npm_install() {
374
384
  command -v npm &>/dev/null || return 1
385
+ if ! _update_global_package_is_active; then
386
+ log_info "Skipping npm install: the active Karnel framework is not npm-managed"
387
+ return 1
388
+ fi
375
389
  log_info "Trying npm install..."
376
390
  if npm install -g karnel-termux@latest --ignore-scripts 2>/dev/null; then
377
391
  local new_ver
@@ -384,6 +398,10 @@ _update_try_npm_install() {
384
398
 
385
399
  _update_try_pnpm() {
386
400
  command -v pnpm &>/dev/null || return 1
401
+ if ! _update_global_package_is_active pnpm; then
402
+ log_info "Skipping pnpm update: the active Karnel framework is not pnpm-managed"
403
+ return 1
404
+ fi
387
405
  log_info "Trying pnpm..."
388
406
  if pnpm add -g karnel-termux@latest 2>/dev/null; then
389
407
  log_success "Updated via pnpm"
@@ -15,11 +15,14 @@ upgrade_main() {
15
15
  return 1
16
16
  fi
17
17
 
18
+ if ! _upgrade_fix_symlink; then
19
+ log_error "Karnel upgrade could not activate its command"
20
+ return 1
21
+ fi
22
+
18
23
  source "$KARNEL_PATH/utils/env.sh" 2>/dev/null
19
24
  log_success "Karnel upgraded to v$KARNEL_VERSION"
20
25
 
21
- _upgrade_fix_symlink
22
-
23
26
  echo
24
27
  log_info "Running cleanup..."
25
28
  import "@/cli/commands/cleanup"
@@ -30,6 +33,10 @@ upgrade_main() {
30
33
  }
31
34
 
32
35
  _upgrade_fix_symlink() {
36
+ if [[ -e "$PREFIX/bin/karnel" && ! -L "$PREFIX/bin/karnel" ]]; then
37
+ log_error "Refusing to replace existing non-symlink: $PREFIX/bin/karnel"
38
+ return 1
39
+ fi
33
40
  if [[ ! -L "$PREFIX/bin/karnel" ]] || [[ "$(readlink "$PREFIX/bin/karnel")" != "$KARNEL_PATH/bin/karnel" ]]; then
34
41
  ln -sf "$KARNEL_PATH/bin/karnel" "$PREFIX/bin/karnel" 2>/dev/null
35
42
  log_success "Symlink updated"
@@ -4,6 +4,7 @@ import "@/utils/log"
4
4
  import "@/utils/colors"
5
5
 
6
6
  LOG_FILE="$KARNEL_CACHE/install_voice.log"
7
+ VOICE_MARKER="${KARNEL_DATA:-${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}/voice/.termux-api-managed"
7
8
 
8
9
  install_voice() {
9
10
  separator
@@ -41,8 +42,10 @@ install_voice() {
41
42
  }
42
43
 
43
44
  _install_voice_deps() {
44
- pkg install termux-api -y &>>"$LOG_FILE"
45
- return $?
45
+ if ! pkg install termux-api -y &>>"$LOG_FILE"; then
46
+ return 1
47
+ fi
48
+ mkdir -p "$(dirname "$VOICE_MARKER")" && : >"$VOICE_MARKER"
46
49
  }
47
50
 
48
51
  _install_voice_shell() {
@@ -122,7 +125,7 @@ reinstall_voice() {
122
125
  }
123
126
 
124
127
  _reinstall_voice_wrapper() {
125
- import "@/tools/voice/all"
126
- reinstall_all_voice_components
127
- return $?
128
+ _uninstall_voice_wrapper || return $?
129
+ _install_voice_deps || return $?
130
+ _install_voice_shell
128
131
  }
@@ -9,6 +9,8 @@ LOG_FILE="$KARNEL_CACHE/install_ai.log"
9
9
  CODEBUFF_DATA_DIR="$HOME/.local/share/karnel-data/codebuff"
10
10
  CODEBUFF_MARKER=".karnel-managed"
11
11
  CODEBUFF_WRAPPER_MARKER="$CODEBUFF_DATA_DIR/.karnel-wrapper"
12
+ CODEBUFF_PREVIOUS_DATA=""
13
+ CODEBUFF_STAGED_WRAPPER=""
12
14
 
13
15
  _codebuff_detect_ubuntu_root() {
14
16
  local root
@@ -115,14 +117,44 @@ _download_codebuff_binary_impl() {
115
117
  fi
116
118
 
117
119
  chmod +x "$staging_dir/codebuff"
118
- replace_managed_directory "$staging_dir" "$CODEBUFF_DATA_DIR" "$CODEBUFF_MARKER"
120
+ if ! _activate_codebuff_payload "$staging_dir"; then
121
+ rm -rf "$staging_dir"
122
+ return 1
123
+ fi
119
124
  }
120
125
 
121
- _compile_codebuff_helper() {
122
- loading "Compiling helper" _compile_codebuff_helper_impl
126
+ _activate_codebuff_payload() {
127
+ local staging_dir="$1" backup=""
128
+ printf '%s\n' 'karnel-managed-v1' >"$staging_dir/$CODEBUFF_MARKER" || return 1
129
+ if [[ -e "$CODEBUFF_DATA_DIR" ]]; then
130
+ if [[ ! -f "$CODEBUFF_DATA_DIR/$CODEBUFF_MARKER" ]]; then
131
+ log_error "Refusing to replace unowned installation: $CODEBUFF_DATA_DIR"
132
+ return 1
133
+ fi
134
+ backup=$(mktemp -d "$(dirname "$CODEBUFF_DATA_DIR")/.codebuff-previous.XXXXXX") || return 1
135
+ rmdir "$backup" || return 1
136
+ mv "$CODEBUFF_DATA_DIR" "$backup" || return 1
137
+ fi
138
+ if ! mv "$staging_dir" "$CODEBUFF_DATA_DIR"; then
139
+ [[ -z "$backup" ]] || mv "$backup" "$CODEBUFF_DATA_DIR"
140
+ return 1
141
+ fi
142
+ if [[ -n "$backup" && -f "$backup/.karnel-wrapper" ]] &&
143
+ ! cp "$backup/.karnel-wrapper" "$CODEBUFF_WRAPPER_MARKER"; then
144
+ rm -rf "$CODEBUFF_DATA_DIR"
145
+ mv "$backup" "$CODEBUFF_DATA_DIR"
146
+ return 1
147
+ fi
148
+ CODEBUFF_PREVIOUS_DATA="$backup"
149
+ }
150
+
151
+ _restore_codebuff_payload() {
152
+ rm -rf "$CODEBUFF_DATA_DIR"
153
+ [[ -z "$CODEBUFF_PREVIOUS_DATA" ]] || mv "$CODEBUFF_PREVIOUS_DATA" "$CODEBUFF_DATA_DIR"
154
+ CODEBUFF_PREVIOUS_DATA=""
123
155
  }
124
156
 
125
- _compile_codebuff_helper_impl() {
157
+ _stage_codebuff_helper() {
126
158
  local HELPER_SRC="$KARNEL_PATH/tools/ai/codebuff/helper/codebuff_helper.c"
127
159
  if [ ! -f "$HELPER_SRC" ]; then
128
160
  log_error "Helper source not found at $HELPER_SRC"
@@ -130,22 +162,50 @@ _compile_codebuff_helper_impl() {
130
162
  fi
131
163
 
132
164
  local staged_wrapper
165
+ if [[ -e "$PREFIX/bin/codebuff" || -L "$PREFIX/bin/codebuff" ]] &&
166
+ ! installer_file_owned "$PREFIX/bin/codebuff" "$CODEBUFF_WRAPPER_MARKER"; then
167
+ log_error "Refusing to replace an existing Codebuff wrapper not owned by Karnel"
168
+ return 1
169
+ fi
133
170
  staged_wrapper=$(mktemp "$PREFIX/bin/.codebuff.XXXXXX") || return 1
134
171
  if ! cc -O2 -o "$staged_wrapper" "$HELPER_SRC" &>>"$LOG_FILE"; then
135
172
  rm -f "$staged_wrapper"
136
173
  log_error "Failed to compile codebuff helper"
137
174
  return 1
138
175
  fi
176
+ chmod +x "$staged_wrapper" || { rm -f "$staged_wrapper"; return 1; }
177
+ CODEBUFF_STAGED_WRAPPER="$staged_wrapper"
178
+ }
139
179
 
140
- chmod +x "$staged_wrapper"
141
- mv "$staged_wrapper" "$PREFIX/bin/codebuff" || return 1
142
- record_managed_file "$PREFIX/bin/codebuff" "$CODEBUFF_WRAPPER_MARKER"
180
+ _activate_codebuff_wrapper() {
181
+ activate_installer_file "$CODEBUFF_STAGED_WRAPPER" "$PREFIX/bin/codebuff" "$CODEBUFF_WRAPPER_MARKER"
182
+ }
183
+
184
+ _compile_codebuff_helper() {
185
+ loading "Compiling helper" _stage_codebuff_helper || return 1
186
+ if ! _activate_codebuff_wrapper; then
187
+ rm -f "$CODEBUFF_STAGED_WRAPPER"
188
+ return 1
189
+ fi
190
+ CODEBUFF_STAGED_WRAPPER=""
143
191
  }
144
192
 
145
193
  _install_codebuff_native() {
194
+ CODEBUFF_PREVIOUS_DATA="" CODEBUFF_STAGED_WRAPPER=""
146
195
  _codebuff_install_deps_native || return 1
147
- _download_codebuff_binary || return 1
148
- _compile_codebuff_helper || return 1
196
+ loading "Compiling helper" _stage_codebuff_helper || return 1
197
+ if ! _download_codebuff_binary; then
198
+ rm -f "$CODEBUFF_STAGED_WRAPPER"
199
+ return 1
200
+ fi
201
+ if ! _activate_codebuff_wrapper; then
202
+ rm -f "$CODEBUFF_STAGED_WRAPPER"
203
+ _restore_codebuff_payload
204
+ return 1
205
+ fi
206
+ CODEBUFF_STAGED_WRAPPER=""
207
+ [[ -z "$CODEBUFF_PREVIOUS_DATA" ]] || rm -rf "$CODEBUFF_PREVIOUS_DATA"
208
+ CODEBUFF_PREVIOUS_DATA=""
149
209
  log_success "Codebuff installed natively"
150
210
  return 0
151
211
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
7
8
 
@@ -39,6 +40,7 @@ _install_codex_npm_impl() {
39
40
  log_error "Failed to install Codex CLI"
40
41
  return 1
41
42
  fi
43
+ _fix_npm_shebang "codex"
42
44
 
43
45
  return 0
44
46
  }
@@ -90,6 +92,7 @@ _update_codex_impl() {
90
92
  log_error "Failed to update Codex CLI"
91
93
  return 1
92
94
  fi
95
+ _fix_npm_shebang "codex"
93
96
  return 0
94
97
  }
95
98
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
7
8
 
@@ -42,6 +43,7 @@ _install_gemini_cli_npm_impl() {
42
43
  log_error "Failed to install Gemini CLI"
43
44
  return 1
44
45
  fi
46
+ _fix_npm_shebang "gemini"
45
47
 
46
48
  return 0
47
49
  }
@@ -97,6 +99,7 @@ _update_gemini_cli_impl() {
97
99
  log_error "Failed to update Gemini CLI"
98
100
  return 1
99
101
  fi
102
+ _fix_npm_shebang "gemini"
100
103
  return 0
101
104
  }
102
105
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
7
8
 
@@ -39,6 +40,7 @@ _install_kimi_code_npm_impl() {
39
40
  log_error "Failed to install Kimi Code"
40
41
  return 1
41
42
  fi
43
+ _fix_npm_shebang "kimi"
42
44
 
43
45
  return 0
44
46
  }
@@ -92,6 +94,7 @@ _update_kimi_code_impl() {
92
94
  log_error "Failed to update Kimi Code"
93
95
  return 1
94
96
  fi
97
+ _fix_npm_shebang "kimi"
95
98
  return 0
96
99
  }
97
100
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
7
8
 
@@ -39,6 +40,7 @@ _install_minimax_cli_npm_impl() {
39
40
  log_error "Failed to install MiniMax CLI"
40
41
  return 1
41
42
  fi
43
+ _fix_npm_shebang "mmx"
42
44
 
43
45
  return 0
44
46
  }
@@ -92,6 +94,7 @@ _update_minimax_cli_impl() {
92
94
  log_error "Failed to update MiniMax CLI"
93
95
  return 1
94
96
  fi
97
+ _fix_npm_shebang "mmx"
95
98
  return 0
96
99
  }
97
100
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
7
8
 
@@ -42,6 +43,7 @@ _install_openclaude_npm_impl() {
42
43
  log_error "Failed to install OpenClaude"
43
44
  return 1
44
45
  fi
46
+ _fix_npm_shebang "openclaude"
45
47
 
46
48
  return 0
47
49
  }
@@ -96,6 +98,7 @@ _update_openclaude_impl() {
96
98
  log_error "Failed to update OpenClaude"
97
99
  return 1
98
100
  fi
101
+ _fix_npm_shebang "openclaude"
99
102
  return 0
100
103
  }
101
104
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
7
8
 
@@ -47,6 +48,7 @@ _install_openclaw_npm_impl() {
47
48
  log_error "Failed to install OpenClaw"
48
49
  return 1
49
50
  fi
51
+ _fix_npm_shebang "openclaw"
50
52
 
51
53
  return 0
52
54
  }
@@ -101,6 +103,7 @@ _update_openclaw_impl() {
101
103
  log_error "Failed to update OpenClaw"
102
104
  return 1
103
105
  fi
106
+ _fix_npm_shebang "openclaw"
104
107
  return 0
105
108
  }
106
109
 
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  import "@/utils/log"
3
3
  import "@/utils/version"
4
+ import "@/utils/npm-shebang"
4
5
 
5
6
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
6
7
 
@@ -39,6 +40,7 @@ _install_pi_npm_impl() {
39
40
  log_error "Failed to install Pi"
40
41
  return 1
41
42
  fi
43
+ _fix_npm_shebang "pi"
42
44
 
43
45
  return 0
44
46
  }
@@ -90,6 +92,7 @@ _update_pi_impl() {
90
92
  log_error "Failed to update Pi"
91
93
  return 1
92
94
  fi
95
+ _fix_npm_shebang "pi"
93
96
  return 0
94
97
  }
95
98
 
@@ -11,6 +11,8 @@ QODER_MANIFEST_URL="https://qoder-ide.oss-accelerate.aliyuncs.com/qodercli/chann
11
11
  QODER_MARKER=".karnel-managed"
12
12
  QODER_WRAPPER_MARKER="$QODER_DATA_DIR/.karnel-wrapper"
13
13
  QODER_PROOT_MARKER="$PREFIX/share/karnel-installers/qoder"
14
+ QODER_PREVIOUS_DATA=""
15
+ QODER_STAGED_WRAPPER=""
14
16
 
15
17
  _qoder_detect_ubuntu_root() {
16
18
  local root
@@ -153,14 +155,44 @@ _download_qoder_binary_impl() {
153
155
  fi
154
156
 
155
157
  chmod +x "$staging_dir/qodercli"
156
- replace_managed_directory "$staging_dir" "$QODER_DATA_DIR" "$QODER_MARKER"
158
+ if ! _activate_qoder_payload "$staging_dir"; then
159
+ rm -rf "$staging_dir"
160
+ return 1
161
+ fi
157
162
  }
158
163
 
159
- _compile_qoder_helper() {
160
- loading "Compiling helper" _compile_qoder_helper_impl
164
+ _activate_qoder_payload() {
165
+ local staging_dir="$1" backup=""
166
+ printf '%s\n' 'karnel-managed-v1' >"$staging_dir/$QODER_MARKER" || return 1
167
+ if [[ -e "$QODER_DATA_DIR" ]]; then
168
+ if [[ ! -f "$QODER_DATA_DIR/$QODER_MARKER" ]]; then
169
+ log_error "Refusing to replace unowned installation: $QODER_DATA_DIR"
170
+ return 1
171
+ fi
172
+ backup=$(mktemp -d "$(dirname "$QODER_DATA_DIR")/.qoder-previous.XXXXXX") || return 1
173
+ rmdir "$backup" || return 1
174
+ mv "$QODER_DATA_DIR" "$backup" || return 1
175
+ fi
176
+ if ! mv "$staging_dir" "$QODER_DATA_DIR"; then
177
+ [[ -z "$backup" ]] || mv "$backup" "$QODER_DATA_DIR"
178
+ return 1
179
+ fi
180
+ if [[ -n "$backup" && -f "$backup/.karnel-wrapper" ]] &&
181
+ ! cp "$backup/.karnel-wrapper" "$QODER_WRAPPER_MARKER"; then
182
+ rm -rf "$QODER_DATA_DIR"
183
+ mv "$backup" "$QODER_DATA_DIR"
184
+ return 1
185
+ fi
186
+ QODER_PREVIOUS_DATA="$backup"
187
+ }
188
+
189
+ _restore_qoder_payload() {
190
+ rm -rf "$QODER_DATA_DIR"
191
+ [[ -z "$QODER_PREVIOUS_DATA" ]] || mv "$QODER_PREVIOUS_DATA" "$QODER_DATA_DIR"
192
+ QODER_PREVIOUS_DATA=""
161
193
  }
162
194
 
163
- _compile_qoder_helper_impl() {
195
+ _stage_qoder_helper() {
164
196
  local HELPER_SRC="$KARNEL_PATH/tools/ai/qoder/helper/qoder_helper.c"
165
197
  if [ ! -f "$HELPER_SRC" ]; then
166
198
  log_error "Helper source not found at $HELPER_SRC"
@@ -168,22 +200,50 @@ _compile_qoder_helper_impl() {
168
200
  fi
169
201
 
170
202
  local staged_wrapper
203
+ if [[ -e "$PREFIX/bin/qodercli" || -L "$PREFIX/bin/qodercli" ]] &&
204
+ ! installer_file_owned "$PREFIX/bin/qodercli" "$QODER_WRAPPER_MARKER"; then
205
+ log_error "Refusing to replace an existing Qoder wrapper not owned by Karnel"
206
+ return 1
207
+ fi
171
208
  staged_wrapper=$(mktemp "$PREFIX/bin/.qodercli.XXXXXX") || return 1
172
209
  if ! cc -O2 -o "$staged_wrapper" "$HELPER_SRC" &>>"$LOG_FILE"; then
173
210
  rm -f "$staged_wrapper"
174
211
  log_error "Failed to compile qoder helper"
175
212
  return 1
176
213
  fi
214
+ chmod +x "$staged_wrapper" || { rm -f "$staged_wrapper"; return 1; }
215
+ QODER_STAGED_WRAPPER="$staged_wrapper"
216
+ }
177
217
 
178
- chmod +x "$staged_wrapper"
179
- mv "$staged_wrapper" "$PREFIX/bin/qodercli" || return 1
180
- record_managed_file "$PREFIX/bin/qodercli" "$QODER_WRAPPER_MARKER"
218
+ _activate_qoder_wrapper() {
219
+ activate_installer_file "$QODER_STAGED_WRAPPER" "$PREFIX/bin/qodercli" "$QODER_WRAPPER_MARKER"
220
+ }
221
+
222
+ _compile_qoder_helper() {
223
+ loading "Compiling helper" _stage_qoder_helper || return 1
224
+ if ! _activate_qoder_wrapper; then
225
+ rm -f "$QODER_STAGED_WRAPPER"
226
+ return 1
227
+ fi
228
+ QODER_STAGED_WRAPPER=""
181
229
  }
182
230
 
183
231
  _install_qoder_native() {
232
+ QODER_PREVIOUS_DATA="" QODER_STAGED_WRAPPER=""
184
233
  _qoder_install_deps_native || return 1
185
- _download_qoder_binary || return 1
186
- _compile_qoder_helper || return 1
234
+ loading "Compiling helper" _stage_qoder_helper || return 1
235
+ if ! _download_qoder_binary; then
236
+ rm -f "$QODER_STAGED_WRAPPER"
237
+ return 1
238
+ fi
239
+ if ! _activate_qoder_wrapper; then
240
+ rm -f "$QODER_STAGED_WRAPPER"
241
+ _restore_qoder_payload
242
+ return 1
243
+ fi
244
+ QODER_STAGED_WRAPPER=""
245
+ [[ -z "$QODER_PREVIOUS_DATA" ]] || rm -rf "$QODER_PREVIOUS_DATA"
246
+ QODER_PREVIOUS_DATA=""
187
247
  log_success "Qoder installed natively"
188
248
  return 0
189
249
  }
@@ -192,6 +252,27 @@ _install_qoder_proot() {
192
252
  loading "Installing Qoder (proot-distro)" _install_qoder_proot_impl
193
253
  }
194
254
 
255
+ _stage_qoder_proot_wrapper() {
256
+ local ubuntu_root="$1" qoder_dir="$2" wrapper_src="$KARNEL_PATH/tools/ai/qoder/bin/qodercli"
257
+ if [[ -e "$PREFIX/bin/qodercli" || -L "$PREFIX/bin/qodercli" ]] &&
258
+ ! installer_file_owned "$PREFIX/bin/qodercli" "$QODER_PROOT_MARKER"; then
259
+ log_error "Refusing to replace an existing Qoder wrapper not owned by Karnel"
260
+ return 1
261
+ fi
262
+ [ -f "$wrapper_src" ] || { log_error "Wrapper template not found at $wrapper_src"; return 1; }
263
+ QODER_STAGED_WRAPPER=$(mktemp "$PREFIX/bin/.qodercli.XXXXXX") || return 1
264
+ if ! sed -e "s|__UBUNTU_ROOTFS__|$ubuntu_root|g" -e "s|__QODER_DIR__|$qoder_dir|g" \
265
+ "$wrapper_src" >"$QODER_STAGED_WRAPPER" || ! chmod +x "$QODER_STAGED_WRAPPER"; then
266
+ rm -f "$QODER_STAGED_WRAPPER"
267
+ QODER_STAGED_WRAPPER=""
268
+ return 1
269
+ fi
270
+ }
271
+
272
+ _activate_qoder_proot_wrapper() {
273
+ activate_installer_file "$QODER_STAGED_WRAPPER" "$PREFIX/bin/qodercli" "$QODER_PROOT_MARKER"
274
+ }
275
+
195
276
  _install_qoder_proot_impl() {
196
277
  case "$(uname -m)" in
197
278
  aarch64|arm64) ;;
@@ -200,16 +281,25 @@ _install_qoder_proot_impl() {
200
281
  mkdir -p "$(dirname "$LOG_FILE")"
201
282
 
202
283
  if ! command -v proot-distro &>/dev/null; then
203
- yes | pkg install proot-distro &>>"$LOG_FILE"
284
+ if ! yes | pkg install proot-distro &>>"$LOG_FILE"; then
285
+ log_error "Failed to install proot-distro"
286
+ return 1
287
+ fi
204
288
  fi
205
289
 
206
290
  if [ ! -d "$(_qoder_detect_ubuntu_root)" ]; then
207
- proot-distro install ubuntu:24.04 &>>"$LOG_FILE"
291
+ if ! proot-distro install ubuntu:24.04 &>>"$LOG_FILE"; then
292
+ log_error "Failed to install Ubuntu Proot"
293
+ return 1
294
+ fi
208
295
  fi
209
296
 
210
- _qoder_proot_ubuntu /bin/bash -c \
297
+ if ! _qoder_proot_ubuntu /bin/bash -c \
211
298
  'apt-get update && apt-get upgrade -y && apt-get install -y curl ca-certificates' \
212
- &>>"$LOG_FILE"
299
+ &>>"$LOG_FILE"; then
300
+ log_error "Failed to prepare Ubuntu Proot"
301
+ return 1
302
+ fi
213
303
 
214
304
  local download_url
215
305
  download_url=$(_get_qoder_download_url "arm64")
@@ -230,45 +320,54 @@ _install_qoder_proot_impl() {
230
320
  log_error "Refusing to replace Qoder Proot data not owned by Karnel"
231
321
  return 1
232
322
  fi
233
- mkdir -p "$qoder_dir"
323
+ if ! _stage_qoder_proot_wrapper "$ubuntu_root" "$qoder_dir"; then
324
+ return 1
325
+ fi
326
+ mkdir -p "$qoder_dir" || { rm -f "$QODER_STAGED_WRAPPER"; return 1; }
234
327
 
235
328
  local archive_filename
236
329
  archive_filename=$(basename "$download_url")
237
330
 
238
331
  if ! curl -fsSL "$download_url" -o "$qoder_dir/$archive_filename" &>>"$LOG_FILE"; then
332
+ rm -rf "$qoder_dir"
333
+ rm -f "$QODER_STAGED_WRAPPER"
239
334
  log_error "Failed to download Qoder binary"
240
335
  return 1
241
336
  fi
242
337
 
243
338
  local expected actual
244
339
  expected=$(_get_qoder_download_sha256 arm64)
245
- actual=$(sha256sum "$qoder_dir/$archive_filename" 2>>"$LOG_FILE") || return 1
340
+ actual=$(sha256sum "$qoder_dir/$archive_filename" 2>>"$LOG_FILE") || { rm -rf "$qoder_dir" "$QODER_STAGED_WRAPPER"; return 1; }
246
341
  if [[ ! "$expected" =~ ^[0-9a-f]{64}$ || "${actual%% *}" != "$expected" ]]; then
247
- rm -f "$qoder_dir/$archive_filename"
342
+ rm -rf "$qoder_dir"
343
+ rm -f "$QODER_STAGED_WRAPPER"
248
344
  log_error "Qoder archive has no valid matching SHA-256 in the official manifest"
249
345
  return 1
250
346
  fi
251
347
  if ! extract_tarball "$qoder_dir/$archive_filename" "$qoder_dir"; then
348
+ rm -rf "$qoder_dir"
349
+ rm -f "$QODER_STAGED_WRAPPER"
252
350
  return 1
253
351
  fi
254
352
 
255
353
  if [ ! -f "$qoder_dir/qodercli" ]; then
354
+ rm -rf "$qoder_dir"
355
+ rm -f "$QODER_STAGED_WRAPPER"
256
356
  log_error "Qoder binary not found after extraction"
257
357
  return 1
258
358
  fi
259
359
 
260
- chmod +x "$qoder_dir/qodercli"
261
- printf '%s\n' 'karnel-managed-v1' >"$qoder_dir/.karnel-managed"
262
-
263
- local wrapper_src="$KARNEL_PATH/tools/ai/qoder/bin/qodercli"
264
- if [ ! -f "$wrapper_src" ]; then
265
- log_error "Wrapper template not found at $wrapper_src"
360
+ if ! chmod +x "$qoder_dir/qodercli" || ! printf '%s\n' 'karnel-managed-v1' >"$qoder_dir/.karnel-managed"; then
361
+ rm -rf "$qoder_dir"
362
+ rm -f "$QODER_STAGED_WRAPPER"
363
+ return 1
364
+ fi
365
+ if ! _activate_qoder_proot_wrapper; then
366
+ rm -f "$QODER_STAGED_WRAPPER"
367
+ rm -rf "$qoder_dir"
266
368
  return 1
267
369
  fi
268
- sed -e "s|__UBUNTU_ROOTFS__|$ubuntu_root|g" -e "s|__QODER_DIR__|$qoder_dir|g" \
269
- "$wrapper_src" >"$PREFIX/bin/qodercli"
270
- chmod +x "$PREFIX/bin/qodercli"
271
- record_managed_file "$PREFIX/bin/qodercli" "$QODER_PROOT_MARKER" || return 1
370
+ QODER_STAGED_WRAPPER=""
272
371
 
273
372
  return 0
274
373
  }
@@ -409,14 +508,26 @@ _update_qoder_proot_impl() {
409
508
  return 1
410
509
  fi
411
510
 
412
- chmod +x "$staging_dir/qodercli"
413
- printf '%s\n' 'karnel-managed-v1' >"$staging_dir/.karnel-managed"
414
- old_dir="${qoder_dir}.previous.$$"
511
+ if ! chmod +x "$staging_dir/qodercli" || ! printf '%s\n' 'karnel-managed-v1' >"$staging_dir/.karnel-managed" ||
512
+ ! _stage_qoder_proot_wrapper "$ubuntu_root" "$qoder_dir"; then
513
+ rm -rf "$staging_dir"
514
+ return 1
515
+ fi
516
+ old_dir=$(mktemp -d "$(dirname "$qoder_dir")/.qoder-previous.XXXXXX") || { rm -f "$QODER_STAGED_WRAPPER"; return 1; }
517
+ rmdir "$old_dir" || { rm -f "$QODER_STAGED_WRAPPER"; return 1; }
415
518
  if ! mv "$qoder_dir" "$old_dir" || ! mv "$staging_dir" "$qoder_dir"; then
416
519
  [[ ! -d "$old_dir" ]] || mv "$old_dir" "$qoder_dir"
417
520
  rm -rf "$staging_dir"
521
+ rm -f "$QODER_STAGED_WRAPPER"
522
+ return 1
523
+ fi
524
+ if ! _activate_qoder_proot_wrapper; then
525
+ rm -f "$QODER_STAGED_WRAPPER"
526
+ rm -rf "$qoder_dir"
527
+ mv "$old_dir" "$qoder_dir"
418
528
  return 1
419
529
  fi
530
+ QODER_STAGED_WRAPPER=""
420
531
  rm -rf "$old_dir"
421
532
 
422
533
  log_success "Qoder (proot-distro) updated"
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_ai.log"
7
8
 
@@ -42,6 +43,7 @@ _install_qwen_code_npm_impl() {
42
43
  log_error "Failed to install Qwen Code"
43
44
  return 1
44
45
  fi
46
+ _fix_npm_shebang "qwen"
45
47
 
46
48
  return 0
47
49
  }
@@ -97,6 +99,7 @@ _update_qwen_code_impl() {
97
99
  log_error "Failed to update Qwen Code"
98
100
  return 1
99
101
  fi
102
+ _fix_npm_shebang "qwen"
100
103
  return 0
101
104
  }
102
105
 
@@ -9,7 +9,9 @@ NETLIFY_VERSION="27.1.1"
9
9
  NETLIFY_MARKER="$PREFIX/share/karnel-installers/netlify"
10
10
 
11
11
  _netlify_is_owned() {
12
- command -v netlify &>/dev/null && [[ -f "$NETLIFY_MARKER" ]] || return 1
12
+ local binary
13
+ binary=$(command -v netlify) || return 1
14
+ managed_file_matches "$binary" "$NETLIFY_MARKER"
13
15
  }
14
16
 
15
17
  _netlify_node_supported() {
@@ -43,12 +45,17 @@ install_netlify() {
43
45
  log_error "Failed to install Netlify CLI (see $LOG_FILE)"
44
46
  return 1
45
47
  fi
46
- command -v termux-fix-shebang &>/dev/null && termux-fix-shebang "$(command -v netlify)" &>/dev/null
48
+ local binary
49
+ binary=$(command -v netlify) || { log_error "Netlify CLI was not found after installation"; return 1; }
50
+ command -v termux-fix-shebang &>/dev/null && termux-fix-shebang "$binary" &>/dev/null
47
51
  if ! netlify --version &>/dev/null; then
48
52
  log_error "Netlify CLI did not start after installation"
49
53
  return 1
50
54
  fi
51
- mkdir -p "$(dirname "$NETLIFY_MARKER")" && : >"$NETLIFY_MARKER"
55
+ if ! record_managed_file "$binary" "$NETLIFY_MARKER"; then
56
+ log_error "Failed to record Netlify CLI ownership"
57
+ return 1
58
+ fi
52
59
  log_success "Netlify CLI installed"
53
60
  }
54
61
 
@@ -8,7 +8,9 @@ LOG_FILE="${LOG_FILE:-${KARNEL_CACHE:-$HOME/.cache/karnel}/install_deploy.log}"
8
8
  VERCEL_MARKER="$PREFIX/share/karnel-installers/vercel"
9
9
 
10
10
  _vercel_is_owned() {
11
- command -v vercel &>/dev/null && [[ -f "$VERCEL_MARKER" ]] || return 1
11
+ local binary
12
+ binary=$(command -v vercel) || return 1
13
+ managed_file_matches "$binary" "$VERCEL_MARKER"
12
14
  }
13
15
 
14
16
  install_vercel() {
@@ -29,8 +31,13 @@ install_vercel() {
29
31
  log_error "Failed to install Vercel CLI (see $LOG_FILE)"
30
32
  return 1
31
33
  fi
32
- command -v termux-fix-shebang &>/dev/null && termux-fix-shebang "$(command -v vercel)" &>/dev/null
33
- mkdir -p "$(dirname "$VERCEL_MARKER")" && : >"$VERCEL_MARKER"
34
+ local binary
35
+ binary=$(command -v vercel) || { log_error "Vercel CLI was not found after installation"; return 1; }
36
+ command -v termux-fix-shebang &>/dev/null && termux-fix-shebang "$binary" &>/dev/null
37
+ if ! record_managed_file "$binary" "$VERCEL_MARKER"; then
38
+ log_error "Failed to record Vercel CLI ownership"
39
+ return 1
40
+ fi
34
41
  log_success "Vercel CLI installed"
35
42
  }
36
43
 
@@ -26,29 +26,7 @@ done
26
26
  unset _tool
27
27
  _register_safe_reinstall_handlers npm "${NODE_PACKAGES[@]}"
28
28
 
29
- _fix_npm_shebang() {
30
- local bin_name="$1"
31
- local bin_path="$PREFIX/bin/$bin_name"
32
- [ ! -f "$bin_path" ] && [ ! -L "$bin_path" ] && return 0
33
-
34
- local real_path
35
- real_path=$(readlink -f "$bin_path" 2>/dev/null || echo "$bin_path")
36
- [ ! -f "$real_path" ] && return 0
37
-
38
- local shebang
39
- shebang=$(head -1 "$real_path")
40
-
41
- case "$shebang" in
42
- "#!/usr/bin/env node"|"#!/data/data/com.termux/files/usr/bin/env node")
43
- sed -i "1s|.*|#!$PREFIX/bin/node|" "$real_path"
44
- log_info "Fixed shebang for $bin_name: $shebang → $PREFIX/bin/node"
45
- ;;
46
- "#!/usr/bin/env bash"|"#!/data/data/com.termux/files/usr/bin/env bash")
47
- sed -i "1s|.*|#!$PREFIX/bin/bash|" "$real_path"
48
- log_info "Fixed shebang for $bin_name: $shebang → $PREFIX/bin/bash"
49
- ;;
50
- esac
51
- }
29
+ import "@/utils/npm-shebang"
52
30
 
53
31
  _batch_npm() {
54
32
  local action="$1"
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -81,6 +82,7 @@ _update_live_server_npm_impl() {
81
82
  log_error "Failed to update Live Server"
82
83
  return 1
83
84
  fi
85
+ _fix_npm_shebang "live-server"
84
86
  return 0
85
87
  }
86
88
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -42,9 +43,9 @@ _install_localtunnel_npm_impl() {
42
43
  log_error "Failed to install Localtunnel"
43
44
  return 1
44
45
  fi
46
+ _fix_npm_shebang "lt"
45
47
  log_info "Applying localtunnel fix for Android..."
46
48
  _localtunnel_fix_openurl &>>"$LOG_FILE"
47
- _fix_npm_shebang "lt"
48
49
  return 0
49
50
  }
50
51
 
@@ -97,6 +98,7 @@ _update_localtunnel_npm_impl() {
97
98
  log_error "Failed to update Localtunnel"
98
99
  return 1
99
100
  fi
101
+ _fix_npm_shebang "lt"
100
102
  return 0
101
103
  }
102
104
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_markserv_npm_impl() {
80
81
  log_error "Failed to update Markserv"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "markserv"
83
85
  return 0
84
86
  }
85
87
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_ncu_npm_impl() {
80
81
  log_error "Failed to update NPM Check Updates"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "ncu"
83
85
  return 0
84
86
  }
85
87
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_nestjs_npm_impl() {
80
81
  log_error "Failed to update NestJS CLI"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "nest"
83
85
  return 0
84
86
  }
85
87
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_ngrok_npm_impl() {
80
81
  log_error "Failed to update Ngrok"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "ngrok"
83
85
  return 0
84
86
  }
85
87
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_prettier_npm_impl() {
80
81
  log_error "Failed to update Prettier"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "prettier"
83
85
  return 0
84
86
  }
85
87
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_psqlformat_npm_impl() {
80
81
  log_error "Failed to update PSQL Format"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "psqlformat"
83
85
  return 0
84
86
  }
85
87
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_typescript_npm_impl() {
80
81
  log_error "Failed to update TypeScript"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "tsc"
83
85
  return 0
84
86
  }
85
87
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/npm-shebang"
5
6
 
6
7
  LOG_FILE="$KARNEL_CACHE/install_npm.log"
7
8
 
@@ -80,6 +81,7 @@ _update_vercel_npm_impl() {
80
81
  log_error "Failed to update Vercel CLI"
81
82
  return 1
82
83
  fi
84
+ _fix_npm_shebang "vercel"
83
85
  return 0
84
86
  }
85
87
 
@@ -1,4 +1,5 @@
1
1
  #!/data/data/com.termux/files/usr/bin/python3
2
+ import os
2
3
  import sys
3
4
  import subprocess
4
5
  import hashlib
@@ -7,12 +7,25 @@ import "@/utils/downloaded-python"
7
7
  LOG_FILE="$KARNEL_CACHE/install_dev.log"
8
8
  DOWNLOAD_URL="https://raw.githubusercontent.com/dedsec1121fk/DedSec/87d69293f4b89b8ab114fa644074629e86313182/Scripts/Network%20Tools/QR%20Code%20Generator.py"
9
9
 
10
+ _qrcode_dependencies() {
11
+ if ! command -v python3 &>/dev/null && ! pkg install python -y &>>"$LOG_FILE"; then
12
+ return 1
13
+ fi
14
+ if ! python3 -c 'import PIL' &>/dev/null && ! pkg install python-pillow -y &>>"$LOG_FILE"; then
15
+ return 1
16
+ fi
17
+ if ! python3 -c 'import qrcode' &>/dev/null && ! python3 -m pip install --no-deps qrcode &>>"$LOG_FILE"; then
18
+ return 1
19
+ fi
20
+ python3 -c 'import qrcode, PIL' &>/dev/null
21
+ }
22
+
10
23
  install_qrcode() {
11
24
  local TOOL_DIR="$KARNEL_DATA/utils/qrcode"
12
25
  local BIN_NAME="qrcode"
13
26
  log_info "Installing QR Code Generator..."
14
27
  mkdir -p "$(dirname "$LOG_FILE")" "$TOOL_DIR"
15
- if ! command -v python3 &>/dev/null; then pkg install python -y &>>"$LOG_FILE" || return 1; fi
28
+ _qrcode_dependencies || { log_error "Failed to install QR Code Generator dependencies"; return 1; }
16
29
  _downloaded_python_install "$BIN_NAME" "$TOOL_DIR" "$DOWNLOAD_URL" || return $?
17
30
  log_success "QR Code Generator installed"
18
31
  return 0
@@ -31,6 +44,7 @@ update_qrcode() {
31
44
  local TOOL_DIR="$KARNEL_DATA/utils/qrcode"
32
45
  local BIN_NAME="qrcode"
33
46
  log_info "Updating QR Code Generator..."
47
+ _qrcode_dependencies || { log_error "Failed to install QR Code Generator dependencies"; return 1; }
34
48
  _downloaded_python_update "$BIN_NAME" "$TOOL_DIR" "$DOWNLOAD_URL" || return 1
35
49
  log_success "QR Code Generator updated"
36
50
  return 0
@@ -1,35 +1,14 @@
1
1
  #!/data/data/com.termux/files/usr/bin/python3
2
+ import os
2
3
  import re
3
- import subprocess
4
4
  import sys
5
5
 
6
- # --- Installation Function (Same as before) ---
7
- def install_and_import(package):
8
- """
9
- Checks if a package is installed and installs it if missing.
10
- The 'qrcode[pil]' installs both qrcode and Pillow (PIL) for image generation.
11
- """
12
- try:
13
- __import__(package)
14
- print(f"✅ Required package '{package}' is already installed.")
15
- except ImportError:
16
- print(f"📦 Package '{package}' not found. Installing now...")
17
- try:
18
- if package == 'qrcode':
19
- # Install qrcode with pil dependency for image saving
20
- subprocess.check_call([sys.executable, "-m", "pip", "install", "qrcode[pil]"])
21
- else:
22
- subprocess.check_call([sys.executable, "-m", "pip", "install", package])
23
-
24
- __import__(package)
25
- print(f"✅ Successfully installed '{package}'.")
26
- except subprocess.CalledProcessError as e:
27
- print(f"❌ Failed to install '{package}'. Error: {e}")
28
- print("Please ensure 'pip' is installed and working in Termux.")
29
- sys.exit(1)
6
+ # The launcher is named qrcode.py, so remove its directory before importing
7
+ # the third-party qrcode package to avoid importing this file recursively.
8
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
9
+ sys.path = [path for path in sys.path if path not in ("", SCRIPT_DIR)]
30
10
 
31
- install_and_import('qrcode')
32
- import qrcode
11
+ import qrcode
33
12
 
34
13
  # ----------------------------------------------------------------------
35
14
  # --- Main Generator Function (Updated Path) ---
@@ -86,4 +65,4 @@ def generate_qr_for_link():
86
65
  print(f"\n❌ Error saving file: {e}")
87
66
 
88
67
  if __name__ == "__main__":
89
- generate_qr_for_link()
68
+ generate_qr_for_link()
@@ -1,4 +1,5 @@
1
1
  #!/data/data/com.termux/files/usr/bin/python3
2
+ import os
2
3
  import re
3
4
  import datetime
4
5
 
@@ -19,6 +19,7 @@ _uninstall_voice_tool() {
19
19
  fi
20
20
  else
21
21
  log_info "Termux:API is not installed"
22
+ rm -f "$marker"
22
23
  fi
23
24
  return 0
24
25
  }
@@ -40,7 +40,7 @@ _downloaded_python_install() {
40
40
  curl -fsSL "$download_url" -o "$staged_payload" || commit_failed=1
41
41
  fi
42
42
  if (( commit_failed != 0 )) || ! sed -i "1s|.*|#!$python3_path|" "$staged_payload" ||
43
- ! chmod +x "$staged_payload" || ! sha256sum "$staged_payload" >"$staged_marker" ||
43
+ ! chmod 755 "$staged_payload" || ! sha256sum "$staged_payload" >"$staged_marker" ||
44
44
  ! ln -s "$payload" "$staged_link"; then
45
45
  rm -rf "$tool_staging" "$link_staging"
46
46
  return 1
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Shared npm shebang repair for Termux.
4
+ # Rewrites `#!/usr/bin/env <interp>` shebangs of npm-installed bins to the
5
+ # absolute Termux interpreter path. Safe to call on any bin name.
6
+
7
+ _fix_npm_shebang() {
8
+ local bin_name="$1"
9
+ local bin_path="$PREFIX/bin/$bin_name"
10
+ [ ! -f "$bin_path" ] && [ ! -L "$bin_path" ] && return 0
11
+
12
+ local real_path
13
+ real_path=$(readlink -f "$bin_path" 2>/dev/null || echo "$bin_path")
14
+ [ ! -f "$real_path" ] && return 0
15
+
16
+ local shebang
17
+ shebang=$(head -1 "$real_path")
18
+
19
+ case "$shebang" in
20
+ "#!/usr/bin/env node"|"#!/data/data/com.termux/files/usr/bin/env node"|"#!$PREFIX/bin/env node")
21
+ sed -i "1s|.*|#!$PREFIX/bin/node|" "$real_path" || return 1
22
+ log_info "Fixed shebang for $bin_name: $shebang → $PREFIX/bin/node"
23
+ ;;
24
+ "#!/usr/bin/env bash"|"#!/data/data/com.termux/files/usr/bin/env bash"|"#!$PREFIX/bin/env bash")
25
+ sed -i "1s|.*|#!$PREFIX/bin/bash|" "$real_path" || return 1
26
+ log_info "Fixed shebang for $bin_name: $shebang → $PREFIX/bin/bash"
27
+ ;;
28
+ esac
29
+ return 0
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karnel-termux",
3
- "version": "4.17.22",
3
+ "version": "4.17.27",
4
4
  "description": "Modular Dev Environment for Termux — install languages, databases, AI agents, editors, and more with one command",
5
5
  "bin": {
6
6
  "karnel": "karnel/bin/karnel"