karnel-termux 4.16.6 → 4.17.0

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 (177) hide show
  1. package/install.sh +5 -5
  2. package/karnel/RELEASE_COMMIT +1 -1
  3. package/karnel/bin/karnel +0 -0
  4. package/karnel/cli/commands/help.sh +0 -0
  5. package/karnel/cli/commands/update.sh +29 -4
  6. package/karnel/tools/ai/all.sh +0 -0
  7. package/karnel/tools/ai/antigravity-cli/install.sh +0 -0
  8. package/karnel/tools/ai/cactus/install.sh +1 -1
  9. package/karnel/tools/ai/claude-code/install.sh +0 -0
  10. package/karnel/tools/ai/cline/bin/cline +0 -0
  11. package/karnel/tools/ai/cline/install.sh +0 -0
  12. package/karnel/tools/ai/codebuff/bin/codebuff +0 -0
  13. package/karnel/tools/ai/codebuff/install.sh +0 -0
  14. package/karnel/tools/ai/codegraph/install.sh +0 -0
  15. package/karnel/tools/ai/codex/install.sh +0 -0
  16. package/karnel/tools/ai/command-code/install.sh +0 -0
  17. package/karnel/tools/ai/copilot-termux/install.sh +0 -0
  18. package/karnel/tools/ai/crush/install.sh +0 -0
  19. package/karnel/tools/ai/ctx7/install.sh +0 -0
  20. package/karnel/tools/ai/engram/install.sh +0 -0
  21. package/karnel/tools/ai/gemini-cli/install.sh +0 -0
  22. package/karnel/tools/ai/gentle-ai/install.sh +0 -0
  23. package/karnel/tools/ai/gga/install.sh +0 -0
  24. package/karnel/tools/ai/gga/termux.patch +0 -0
  25. package/karnel/tools/ai/hermes-agent/install.sh +0 -0
  26. package/karnel/tools/ai/kilocode-cli/install.sh +0 -0
  27. package/karnel/tools/ai/kimchi-code/install.sh +6 -0
  28. package/karnel/tools/ai/kimi-code/install.sh +0 -0
  29. package/karnel/tools/ai/kiro/install.sh +0 -0
  30. package/karnel/tools/ai/mimocode/install.sh +0 -0
  31. package/karnel/tools/ai/minimax-cli/install.sh +0 -0
  32. package/karnel/tools/ai/mistral-vibe/install.sh +0 -0
  33. package/karnel/tools/ai/odysseus/install.sh +0 -0
  34. package/karnel/tools/ai/ollama/install.sh +0 -0
  35. package/karnel/tools/ai/omni-route/install.sh +0 -0
  36. package/karnel/tools/ai/openclaude/install.sh +0 -0
  37. package/karnel/tools/ai/openclaw/install.sh +0 -0
  38. package/karnel/tools/ai/opencode/install.sh +13 -2
  39. package/karnel/tools/ai/openspec/install.sh +0 -0
  40. package/karnel/tools/ai/pi/install.sh +0 -0
  41. package/karnel/tools/ai/qwen-code/install.sh +0 -0
  42. package/karnel/tools/ai/walkie/install.sh +4 -2
  43. package/karnel/tools/auto/n8n/install.sh +0 -0
  44. package/karnel/tools/db/mariadb/install.sh +0 -0
  45. package/karnel/tools/db/mongodb/install.sh +0 -0
  46. package/karnel/tools/db/postgresql/install.sh +0 -0
  47. package/karnel/tools/db/redis/install.sh +0 -0
  48. package/karnel/tools/db/sqlite/install.sh +0 -0
  49. package/karnel/tools/deploy/netlify/install.sh +0 -0
  50. package/karnel/tools/deploy/railway/install.sh +0 -0
  51. package/karnel/tools/deploy/vercel/install.sh +0 -0
  52. package/karnel/tools/dev/bat/install.sh +0 -0
  53. package/karnel/tools/dev/bc/install.sh +0 -0
  54. package/karnel/tools/dev/cloudflared/install.sh +0 -0
  55. package/karnel/tools/dev/curl/install.sh +0 -0
  56. package/karnel/tools/dev/fzf/install.sh +0 -0
  57. package/karnel/tools/dev/gh/install.sh +0 -0
  58. package/karnel/tools/dev/html2text/install.sh +0 -0
  59. package/karnel/tools/dev/imagemagick/install.sh +0 -0
  60. package/karnel/tools/dev/jq/install.sh +0 -0
  61. package/karnel/tools/dev/lsd/install.sh +0 -0
  62. package/karnel/tools/dev/make/install.sh +0 -0
  63. package/karnel/tools/dev/ncurses/install.sh +0 -0
  64. package/karnel/tools/dev/openssh/install.sh +0 -0
  65. package/karnel/tools/dev/proot/install.sh +0 -0
  66. package/karnel/tools/dev/shfmt/install.sh +0 -0
  67. package/karnel/tools/dev/snyk/install.sh +0 -0
  68. package/karnel/tools/dev/tmate/install.sh +0 -0
  69. package/karnel/tools/dev/tmux/install.sh +0 -0
  70. package/karnel/tools/dev/translate/install.sh +0 -0
  71. package/karnel/tools/dev/tree/install.sh +0 -0
  72. package/karnel/tools/dev/udocker/install.sh +0 -0
  73. package/karnel/tools/dev/wget/install.sh +0 -0
  74. package/karnel/tools/editor/code-server/install.sh +0 -0
  75. package/karnel/tools/editor/neovim/install.sh +0 -0
  76. package/karnel/tools/editor/nvchad/install.sh +0 -0
  77. package/karnel/tools/games/arcade/install.sh +0 -0
  78. package/karnel/tools/games/buzz/install.sh +0 -0
  79. package/karnel/tools/games/ctfgod/install.sh +0 -0
  80. package/karnel/tools/games/detective/install.sh +0 -0
  81. package/karnel/tools/games/pet-friends/install.sh +0 -0
  82. package/karnel/tools/games/tamagotchi/install.sh +0 -0
  83. package/karnel/tools/lang/bun/bin/bunx +0 -0
  84. package/karnel/tools/lang/bun/install.sh +0 -0
  85. package/karnel/tools/lang/clang/install.sh +0 -0
  86. package/karnel/tools/lang/golang/install.sh +0 -0
  87. package/karnel/tools/lang/nodejs/install.sh +0 -0
  88. package/karnel/tools/lang/perl/install.sh +0 -0
  89. package/karnel/tools/lang/php/install.sh +0 -0
  90. package/karnel/tools/lang/python/install.sh +0 -0
  91. package/karnel/tools/lang/rust/install.sh +0 -0
  92. package/karnel/tools/network/dark/install.sh +0 -0
  93. package/karnel/tools/network/dedsec-network/install.sh +0 -0
  94. package/karnel/tools/npm/live-server/install.sh +0 -0
  95. package/karnel/tools/npm/localtunnel/install.sh +0 -0
  96. package/karnel/tools/npm/markserv/install.sh +0 -0
  97. package/karnel/tools/npm/ncu/install.sh +0 -0
  98. package/karnel/tools/npm/nestjs/install.sh +0 -0
  99. package/karnel/tools/npm/ngrok/install.sh +0 -0
  100. package/karnel/tools/npm/prettier/install.sh +0 -0
  101. package/karnel/tools/npm/psqlformat/install.sh +0 -0
  102. package/karnel/tools/npm/turbopack/install.sh +0 -0
  103. package/karnel/tools/npm/typescript/install.sh +0 -0
  104. package/karnel/tools/npm/vercel/install.sh +0 -0
  105. package/karnel/tools/osint/robin/common.sh +0 -0
  106. package/karnel/tools/osint/robin/install.sh +0 -0
  107. package/karnel/tools/security/aircrack-ng/install.sh +0 -0
  108. package/karnel/tools/security/amass/install.sh +0 -0
  109. package/karnel/tools/security/binwalk/install.sh +0 -0
  110. package/karnel/tools/security/burpsuite/install.sh +0 -0
  111. package/karnel/tools/security/dirb/install.sh +0 -0
  112. package/karnel/tools/security/dnsrecon/install.sh +0 -0
  113. package/karnel/tools/security/enum4linux/install.sh +0 -0
  114. package/karnel/tools/security/exiftool/install.sh +0 -0
  115. package/karnel/tools/security/ffuf/install.sh +0 -0
  116. package/karnel/tools/security/foremost/install.sh +0 -0
  117. package/karnel/tools/security/gobuster/install.sh +0 -0
  118. package/karnel/tools/security/hashcat/install.sh +0 -0
  119. package/karnel/tools/security/hydra/install.sh +0 -0
  120. package/karnel/tools/security/john/install.sh +0 -0
  121. package/karnel/tools/security/masscan/install.sh +0 -0
  122. package/karnel/tools/security/metasploit/install.sh +0 -0
  123. package/karnel/tools/security/netcat/install.sh +0 -0
  124. package/karnel/tools/security/nikto/install.sh +0 -0
  125. package/karnel/tools/security/nmap/install.sh +0 -0
  126. package/karnel/tools/security/smbclient/install.sh +0 -0
  127. package/karnel/tools/security/sqlmap/install.sh +0 -0
  128. package/karnel/tools/security/steghide/install.sh +0 -0
  129. package/karnel/tools/security/subfinder/install.sh +0 -0
  130. package/karnel/tools/security/tcpdump/install.sh +0 -0
  131. package/karnel/tools/security/theharvester/install.sh +0 -0
  132. package/karnel/tools/security/wafw00f/install.sh +0 -0
  133. package/karnel/tools/security/whatweb/install.sh +0 -0
  134. package/karnel/tools/security/whois/install.sh +0 -0
  135. package/karnel/tools/security/wpscan/install.sh +0 -0
  136. package/karnel/tools/security/zap/install.sh +0 -0
  137. package/karnel/tools/shell/better-npm/install.sh +0 -0
  138. package/karnel/tools/shell/fzf-tab/install.sh +0 -0
  139. package/karnel/tools/shell/history-substring/install.sh +0 -0
  140. package/karnel/tools/shell/powerlevel10k/install.sh +0 -0
  141. package/karnel/tools/shell/you-should-use/install.sh +0 -0
  142. package/karnel/tools/shell/zsh-autopair/install.sh +0 -0
  143. package/karnel/tools/shell/zsh-autosuggestions/install.sh +0 -0
  144. package/karnel/tools/shell/zsh-completions/install.sh +0 -0
  145. package/karnel/tools/shell/zsh-defer/install.sh +0 -0
  146. package/karnel/tools/shell/zsh-syntax-highlighting/install.sh +0 -0
  147. package/karnel/tools/ui/banner/install.sh +0 -0
  148. package/karnel/tools/ui/cursor/install.sh +0 -0
  149. package/karnel/tools/ui/extra-keys/install.sh +0 -0
  150. package/karnel/tools/ui/font/install.sh +0 -0
  151. package/karnel/tools/utils/applaunch/applaunch.py +0 -0
  152. package/karnel/tools/utils/applaunch/install.sh +0 -0
  153. package/karnel/tools/utils/fconv/fconv.py +0 -0
  154. package/karnel/tools/utils/fconv/install.sh +0 -0
  155. package/karnel/tools/utils/filecheck/filecheck.py +0 -0
  156. package/karnel/tools/utils/filecheck/install.sh +0 -0
  157. package/karnel/tools/utils/httptmux/install.sh +0 -0
  158. package/karnel/tools/utils/notes/install.sh +0 -0
  159. package/karnel/tools/utils/notes/notes.py +0 -0
  160. package/karnel/tools/utils/passman/install.sh +0 -0
  161. package/karnel/tools/utils/passman/passman.py +0 -0
  162. package/karnel/tools/utils/qrcode/install.sh +0 -0
  163. package/karnel/tools/utils/qrcode/qrcode.py +0 -0
  164. package/karnel/tools/utils/splash/install.sh +0 -0
  165. package/karnel/tools/utils/splash/splash.py +0 -0
  166. package/karnel/tools/utils/treex/install.sh +0 -0
  167. package/karnel/tools/utils/treex/treex.py +0 -0
  168. package/karnel/tools/utils/websites/install.sh +0 -0
  169. package/karnel/tools/utils/websites/websites.py +0 -0
  170. package/karnel/tools/utils/zork/install.sh +0 -0
  171. package/karnel/tools/utils/zork/zork.sh +0 -0
  172. package/karnel/utils/agent_actions.sh +46 -5
  173. package/karnel/utils/agent_llm.sh +64 -28
  174. package/karnel/utils/bootstrap.sh +4 -2
  175. package/package.json +1 -1
  176. package/scripts/check-package.js +15 -5
  177. package/scripts/npm-install.js +20 -1
package/install.sh CHANGED
@@ -145,7 +145,7 @@ bootstrap_dependencies() {
145
145
 
146
146
  if [[ $needed_tput -eq 1 ]]; then
147
147
  echo -e " ${P_BORDER}→${P_NC} Installing ncurses-utils..."
148
- pkg install -y ncurses-utils &>/dev/null
148
+ pkg install -y ncurses-utils &>/dev/null || true
149
149
  echo -e " ${P_OK}✔${P_NC} ncurses installed"
150
150
  echo
151
151
  fi
@@ -153,7 +153,7 @@ bootstrap_dependencies() {
153
153
  if [[ $needed_git -eq 1 ]]; then
154
154
  log_info "Installing git..."
155
155
  progress_bar 0 10
156
- pkg install -y git &>/dev/null
156
+ pkg install -y git &>/dev/null || true
157
157
  progress_bar 10 10
158
158
  echo
159
159
  log_ok "git installed"
@@ -162,7 +162,7 @@ bootstrap_dependencies() {
162
162
  if [[ $needed_glow -eq 1 ]]; then
163
163
  log_info "Installing glow..."
164
164
  progress_bar 0 10
165
- pkg install -y glow &>/dev/null
165
+ pkg install -y glow &>/dev/null || true
166
166
  progress_bar 10 10
167
167
  echo
168
168
  log_ok "glow installed"
@@ -171,7 +171,7 @@ bootstrap_dependencies() {
171
171
  if [[ $needed_gh -eq 1 ]]; then
172
172
  log_info "Installing gh (GitHub CLI)..."
173
173
  progress_bar 0 10
174
- pkg install -y gh &>/dev/null
174
+ pkg install -y gh &>/dev/null || true
175
175
  progress_bar 10 10
176
176
  echo
177
177
  log_ok "gh installed"
@@ -180,7 +180,7 @@ bootstrap_dependencies() {
180
180
  if [[ $needed_rg -eq 1 ]]; then
181
181
  log_info "Installing ripgrep..."
182
182
  progress_bar 0 10
183
- pkg install -y ripgrep &>/dev/null
183
+ pkg install -y ripgrep &>/dev/null || true
184
184
  progress_bar 10 10
185
185
  echo
186
186
  log_ok "ripgrep installed"
@@ -1 +1 @@
1
- 8fe9d579a4a8d7fbcab5a8495b1bba025dc684a9
1
+ 57a1aa3e5401b6ba9b37965d8680bcb543b0fc00
package/karnel/bin/karnel CHANGED
File without changes
File without changes
@@ -210,6 +210,18 @@ _update_cleanup() {
210
210
  rm -f "$KARNEL_CACHE/new_version" "$KARNEL_CACHE/last_version_check"
211
211
  }
212
212
 
213
+ _update_release_commit() {
214
+ local tag="$1" commit=""
215
+ if command -v git &>/dev/null 2>&1; then
216
+ commit=$(git ls-remote "https://github.com/israelmarques1024-dotcom/karnel-termux.git" "refs/tags/${tag}^{}" 2>/dev/null | awk '{print $1}')
217
+ fi
218
+ if [[ -z "$commit" ]] && command -v curl &>/dev/null 2>&1; then
219
+ commit=$(curl -fsS -m 10 "https://api.github.com/repos/israelmarques1024-dotcom/karnel-termux/commits/${tag}" 2>/dev/null \
220
+ | grep -m1 '"sha"' | sed -E 's/.*"sha":[[:space:]]*"([0-9a-f]{40})".*/\1/')
221
+ fi
222
+ [[ "$commit" =~ ^[0-9a-f]{40}$ ]] && echo "$commit"
223
+ }
224
+
213
225
  _update_try_curl() {
214
226
  command -v curl &>/dev/null || return 1
215
227
 
@@ -252,10 +264,23 @@ _update_try_curl() {
252
264
 
253
265
  rm -f "$meta" "$sumfile"
254
266
 
255
- if bash "$installer" --ref "$tag"; then
256
- rm -f "$installer"
257
- log_success "Karnel-Termux updated via curl ($tag)"
258
- return 0
267
+ # Resolve the release's target commit so the installer's "immutable
268
+ # commit SHA" requirement is satisfied even when the downloaded asset
269
+ # does not have RELEASE_COMMIT baked in.
270
+ local commit
271
+ commit=$(_update_release_commit "$tag")
272
+ if [[ -n "$commit" ]]; then
273
+ if bash "$installer" --ref "$tag" --commit "$commit"; then
274
+ rm -f "$installer"
275
+ log_success "Karnel-Termux updated via curl ($tag)"
276
+ return 0
277
+ fi
278
+ else
279
+ if bash "$installer" --ref "$tag"; then
280
+ rm -f "$installer"
281
+ log_success "Karnel-Termux updated via curl ($tag)"
282
+ return 0
283
+ fi
259
284
  fi
260
285
 
261
286
  rm -f "$installer"
File without changes
File without changes
@@ -191,7 +191,7 @@ _cactus_write_wrapper() {
191
191
  # The server loads the engine via ctypes inside the python process, so the
192
192
  # whole interpreter must run under qemu-aarch64; CACTUS_EMULATE=1 makes the
193
193
  # CLI wrap native subprocess children (bin/run) with qemu as well.
194
- exec proot-distro login --shared-tmp ubuntu -- env CACTUS_EMULATE=1 qemu-aarch64 -cpu max -L / $CACTUS_CONTAINER_DIR/venv/bin/python -m cactus "\$@"
194
+ exec proot-distro login --shared-tmp ubuntu -- env CACTUS_EMULATE=1${CACTUS_CLOUD_KEY:+ CACTUS_CLOUD_KEY="$CACTUS_CLOUD_KEY"}${CACTUS_BASE_URL:+ CACTUS_BASE_URL="$CACTUS_BASE_URL"} qemu-aarch64 -cpu max -L / $CACTUS_CONTAINER_DIR/venv/bin/python -m cactus "$@"
195
195
  EOF
196
196
  else
197
197
  cat >"$temporary" <<EOF
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -394,7 +394,13 @@ uninstall_kimchi_code() {
394
394
  "$HOME/.local/share/kimchi" \
395
395
  "$HOME/.local/state/kimchi" \
396
396
  "$HOME/.cache/kimchi"
397
+ # Also remove the binaries installed inside the ubuntu container for
398
+ # proot/glibc installs (they are not cleaned by _uninstall_kimchi_impl).
399
+ if [ -n "$(_kimchi_detect_ubuntu_root)" ] && _kimchi_wrapper_owned; then
400
+ _kimchi_proot_ubuntu /bin/bash -c 'rm -f /usr/local/bin/kimchi && rm -rf /usr/local/share/kimchi' &>>"$LOG_FILE"
401
+ fi
397
402
  loading "Uninstalling Kimchi" _uninstall_kimchi_impl
403
+ rm -f "$PREFIX/bin/kimchi"
398
404
  return $?
399
405
  fi
400
406
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -96,6 +96,8 @@ _install_opencode_proot_impl() {
96
96
  npm install -g opencode-ai@1.18.15
97
97
  ' &>>"$LOG_FILE"
98
98
 
99
+ proot_ubuntu /bin/bash -c 'touch /root/.opencode/.karnel-managed' &>>"$LOG_FILE"
100
+
99
101
  local ubuntu_root
100
102
  ubuntu_root="$(detect_ubuntu_root)"
101
103
  if [ -z "$ubuntu_root" ]; then
@@ -153,7 +155,11 @@ uninstall_opencode() {
153
155
  return 0
154
156
  fi
155
157
 
156
- proot_ubuntu /bin/bash -c 'rm -rf /root/.opencode' &>>"$LOG_FILE"
158
+ if proot_ubuntu test -f /root/.opencode/.karnel-managed &>>"$LOG_FILE"; then
159
+ proot_ubuntu /bin/bash -c 'rm -rf /root/.opencode' &>>"$LOG_FILE"
160
+ else
161
+ log_warn "OpenCode data in the Ubuntu container was not installed by Karnel — leaving it untouched"
162
+ fi
157
163
  local ubuntu_root
158
164
  ubuntu_root="$(detect_ubuntu_root)/root/.bashrc"
159
165
  if [ -f "$ubuntu_root" ]; then
@@ -179,7 +185,12 @@ _do_update_opencode() {
179
185
  return $?
180
186
  fi
181
187
 
182
- proot_ubuntu /bin/bash -c 'rm -rf /root/.opencode' &>>"$LOG_FILE"
188
+ if proot_ubuntu test -f /root/.opencode/.karnel-managed &>>"$LOG_FILE"; then
189
+ proot_ubuntu /bin/bash -c 'rm -rf /root/.opencode' &>>"$LOG_FILE"
190
+ else
191
+ log_warn "Existing OpenCode in the Ubuntu container was not installed by Karnel — aborting update to avoid data loss"
192
+ return 1
193
+ fi
183
194
  proot_ubuntu /bin/bash -c '
184
195
  export SHELL=/bin/bash
185
196
  export TMPDIR=/tmp
File without changes
File without changes
File without changes
@@ -646,8 +646,10 @@ update_walkie() {
646
646
  return 0
647
647
  fi
648
648
  log_info "New walkie revision available: $remote_commit (installed: $installed_commit)"
649
- local answer
650
- read -r -p "Update walkie? [Y/n] " answer
649
+ local answer="y"
650
+ if [[ -z "$KARNEL_NONINTERACTIVE" && -t 0 ]]; then
651
+ read -r -p "Update walkie? [Y/n] " answer
652
+ fi
651
653
  case "$answer" in
652
654
  ""|y|Y|yes|Yes|YES) _update_walkie ;;
653
655
  *) log_info "Update skipped" ;;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -378,12 +378,19 @@ agent_apply_files() {
378
378
  fi
379
379
  while IFS=$'\t' read -r n path lang; do
380
380
  [[ -z "$n" ]] && continue
381
- # resolve relative paths against the workspace (absolute paths are used as-is)
381
+ # Resolve the destination and CONFINE it to the workspace. Absolute
382
+ # paths and ".." escapes are rejected so the model cannot overwrite
383
+ # arbitrary files (e.g. shell rc files outside the project).
382
384
  if [[ "$path" != /* ]]; then
383
385
  target="$ws/$path"
384
386
  else
385
387
  target="$path"
386
388
  fi
389
+ target_abs="$(realpath -m "$target" 2>/dev/null)"
390
+ if [[ "$target_abs" != "$ws"/* ]]; then
391
+ log_error "Refusing to write outside the workspace: $path"
392
+ continue
393
+ fi
387
394
  # write-protected: the model must not create/edit a file the task
388
395
  # only references (delete/move/run...). Skip it silently.
389
396
  if (( ${#noread[@]} > 0 )); then
@@ -411,6 +418,21 @@ agent_apply_files() {
411
418
  # during a read/explain task) — nothing to create or edit
412
419
  continue
413
420
  fi
421
+ # Overwriting an existing file requires approval (mirrors the
422
+ # command confirmation flow). New files inside the workspace are
423
+ # applied automatically.
424
+ if (( existed )) && [[ "$AGENT_CONFIRM_COMMANDS" == "1" && "$AGENT_YES" != "1" ]]; then
425
+ if [[ -t 0 ]]; then
426
+ local _wf
427
+ if ! agent_confirm "Overwrite $target?" _wf; then
428
+ log_warn "Skipped overwriting $target"
429
+ continue
430
+ fi
431
+ else
432
+ log_warn "No terminal — skipping overwrite: $target (use -y to auto-approve)"
433
+ continue
434
+ fi
435
+ fi
414
436
  if cat "$d/action_file_$n" >"$target" 2>/dev/null; then
415
437
  if (( shown == 0 )); then
416
438
  shown=1
@@ -453,8 +475,12 @@ _agent_cmd_is_write() {
453
475
  if printf '%s\n' "$cmd" | grep -qE '(^|[;&|[:space:]])(>|>>)[^=&]'; then
454
476
  return 0
455
477
  fi
456
- # blocklist of commands that write to disk / change state
457
- printf '%s\n' "$cmd" | grep -qiE '(^|[;&|[:space:]])(rm|rmdir|unlink|mv|cp|mkdir|touch|ln|chmod|chown|chgrp|install|truncate|tee|dd|mkfs|mount|umount|tar|gzip|gunzip|bzip2|xz|zstd|curl|wget|python3|node|npm|bun|yarn|pnpm|cargo|go |rustc|git (add|commit|push|pull|merge|rebase|reset|checkout|switch|restore|clean|stash|tag|clone|init|rm|mv)|pkg (install|uninstall|upgrade|reinstall|update)|apt( |-)|apt-get|dpkg|yum|dnf|pacman|brew|pip|pip3|uv|conda|mysql|psql|sqlite3|redis-cli|kill|pkill|killall|service|systemctl|halt|reboot|shutdown|sudo|doas|su)[[:space:]]' && return 0
478
+ # command substitution / backticks execute arbitrary code write
479
+ if printf '%s\n' "$cmd" | grep -qE '\$\(|`'; then
480
+ return 0
481
+ fi
482
+ # blocklist of commands that write to disk / change state / spawn a shell
483
+ printf '%s\n' "$cmd" | grep -qiE '(^|[;&|[:space:]])(rm|rmdir|unlink|mv|cp|mkdir|touch|ln|chmod|chown|chgrp|install|truncate|tee|dd|mkfs|mount|umount|tar|gzip|gunzip|bzip2|xz|zstd|curl|wget|python3|python|node|npm|bun|yarn|pnpm|cargo|go |rustc|git (add|commit|push|pull|merge|rebase|reset|checkout|switch|restore|clean|stash|tag|clone|init|rm|mv)|pkg (install|uninstall|upgrade|reinstall|update)|apt( |-)|apt-get|dpkg|yum|dnf|pacman|brew|pip|pip3|uv|conda|mysql|psql|sqlite3|redis-cli|kill|pkill|killall|service|systemctl|halt|reboot|shutdown|sudo|doas|su|pkexec|chroot|bash|sh|dash|zsh|ksh|eval|source|exec|screen|tmux)[[:space:]]' && return 0
458
484
  return 1
459
485
  }
460
486
 
@@ -1107,7 +1133,11 @@ agent_voice_capture() {
1107
1133
  # left alone.
1108
1134
  # ------------------------------------------------------------
1109
1135
  AGENT_SERVER_LOG="$KARNEL_CACHE/karnel-agent.log"
1110
- AGENT_SERVER_PID_FILE="$KARNEL_CACHE/agent/server.pid"
1136
+ # Per-session PID file so one `karnel agent` session does not tear down a
1137
+ # server started by a different session. AGENT_SERVER_OWNED tracks whether
1138
+ # THIS session launched the server (only then do we stop it on exit).
1139
+ AGENT_SERVER_PID_FILE="$KARNEL_CACHE/agent/server_$$.pid"
1140
+ AGENT_SERVER_OWNED="${AGENT_SERVER_OWNED:-0}"
1111
1141
 
1112
1142
  agent_server_running() {
1113
1143
  [[ -f "$AGENT_SERVER_PID_FILE" ]] || return 1
@@ -1168,11 +1198,19 @@ agent_server_ensure() {
1168
1198
  list_item "Logs: ${D_CYAN}$AGENT_SERVER_LOG${D_NC}"
1169
1199
  mkdir -p "$(dirname "$AGENT_SERVER_PID_FILE")"
1170
1200
  rm -f "$AGENT_SERVER_PID_FILE"
1201
+ AGENT_SERVER_OWNED=1
1202
+ # Cloud handoff: if a Cactus Cloud key is present, drop --no-cloud-handoff
1203
+ # so the local server proxies inference to the cloud (fast on this device).
1204
+ # Keyless users keep the fully-local path.
1205
+ local server_cmd="$AGENT_SERVER_CMD"
1206
+ if [[ -n "${CACTUS_CLOUD_KEY:-}" ]]; then
1207
+ server_cmd="${server_cmd//--no-cloud-handoff/}"
1208
+ fi
1171
1209
  # stdin from /dev/null so proot-distro (used by the cactus wrapper)
1172
1210
  # never blocks waiting for the terminal. The background wrapper writes
1173
1211
  # its OWN real PID (setsid may fork, so $! can point at a dead
1174
1212
  # intermediate); `exec` keeps that PID for the actual server.
1175
- setsid bash -c 'echo $$ > "$1"; shift; exec "$@"' _ "$AGENT_SERVER_PID_FILE" $AGENT_SERVER_CMD </dev/null >"$AGENT_SERVER_LOG" 2>&1 &
1213
+ setsid bash -c 'echo $$ > "$1"; shift; exec "$@"' _ "$AGENT_SERVER_PID_FILE" $server_cmd </dev/null >"$AGENT_SERVER_LOG" 2>&1 &
1176
1214
  # wait for the wrapper to write its PID before we can track/stop it
1177
1215
  local i
1178
1216
  for ((i = 0; i < 5; i++)); do
@@ -1187,6 +1225,9 @@ agent_server_ensure() {
1187
1225
  }
1188
1226
 
1189
1227
  agent_server_stop() {
1228
+ # Only stop a server THIS session started; a server launched by
1229
+ # another session (or manually) is left untouched.
1230
+ [[ "$AGENT_SERVER_OWNED" == "1" ]] || return 0
1190
1231
  [[ -f "$AGENT_SERVER_PID_FILE" ]] || return 0
1191
1232
  local pid
1192
1233
  pid=$(cat "$AGENT_SERVER_PID_FILE" 2>/dev/null)
@@ -20,7 +20,7 @@ AGENT_MAX_ITERATIONS="${AGENT_MAX_ITERATIONS:-12}"
20
20
  AGENT_WORKSPACE="${AGENT_WORKSPACE:-$PWD}"
21
21
  AGENT_CONFIRM_COMMANDS="${AGENT_CONFIRM_COMMANDS:-1}"
22
22
  AGENT_CONTEXT_WINDOW="${AGENT_CONTEXT_WINDOW:-8192}"
23
- AGENT_SERVER_CMD="${AGENT_SERVER_CMD:-cactus serve Cactus-Compute/gemma-4-E2B-it --host 127.0.0.1 --port 8000 --no-cloud-handoff}"
23
+ AGENT_SERVER_CMD="${AGENT_SERVER_CMD:-cactus serve Cactus-Compute/gemma-4-e2b-it-cq4 --host 127.0.0.1 --port 8000 --no-cloud-handoff}"
24
24
 
25
25
  # ------------------------------------------------------------
26
26
  # agent_config_load — read persisted config
@@ -36,14 +36,24 @@ agent_config_load() {
36
36
  AGENT_TEMPERATURE="${AGENT_TEMPERATURE:-0.3}"
37
37
  AGENT_MAX_TOKENS="${AGENT_MAX_TOKENS:-2048}"
38
38
  AGENT_MAX_ITERATIONS="${AGENT_MAX_ITERATIONS:-12}"
39
- # the agent ALWAYS starts in the directory you run it from — the
40
- # workspace is never loaded from the persisted config (a stale
41
- # saved path used to pin the agent to $HOME/karnel-data). Override
42
- # it per session with `-w/--workspace` or `/workspace`.
43
- AGENT_WORKSPACE="$PWD"
39
+ # Honour a saved workspace ONLY when the user explicitly configured
40
+ # one (the persisted file contains AGENT_WORKSPACE=) and it still
41
+ # exists. Otherwise the agent always starts in the current directory.
42
+ if grep -q '^AGENT_WORKSPACE=' "$AGENT_CONF_FILE" 2>/dev/null; then
43
+ [[ -d "$AGENT_WORKSPACE" ]] || AGENT_WORKSPACE="$PWD"
44
+ else
45
+ AGENT_WORKSPACE="$PWD"
46
+ fi
44
47
  AGENT_CONFIRM_COMMANDS="${AGENT_CONFIRM_COMMANDS:-1}"
45
48
  AGENT_CONTEXT_WINDOW="${AGENT_CONTEXT_WINDOW:-8192}"
46
- AGENT_SERVER_CMD="${AGENT_SERVER_CMD:-cactus serve Cactus-Compute/gemma-4-E2B-it --host 127.0.0.1 --port 8000 --no-cloud-handoff}"
49
+ AGENT_SERVER_CMD="${AGENT_SERVER_CMD:-cactus serve Cactus-Compute/gemma-4-e2b-it-cq4 --host 127.0.0.1 --port 8000 --no-cloud-handoff}"
50
+ # Reject tampered config (defense-in-depth): numeric/boolean fields
51
+ # must match the expected shape or they are reset to safe defaults.
52
+ [[ "$AGENT_TEMPERATURE" =~ ^[0-9]+(\.[0-9]+)?$ ]] || AGENT_TEMPERATURE=0.3
53
+ [[ "$AGENT_MAX_TOKENS" =~ ^[0-9]+$ ]] || AGENT_MAX_TOKENS=2048
54
+ [[ "$AGENT_MAX_ITERATIONS" =~ ^[0-9]+$ ]] || AGENT_MAX_ITERATIONS=12
55
+ [[ "$AGENT_CONFIRM_COMMANDS" =~ ^[01]$ ]] || AGENT_CONFIRM_COMMANDS=1
56
+ [[ "$AGENT_CONTEXT_WINDOW" =~ ^[0-9]+$ ]] || AGENT_CONTEXT_WINDOW=8192
47
57
  }
48
58
 
49
59
  # ------------------------------------------------------------
@@ -51,17 +61,19 @@ AGENT_SERVER_CMD="${AGENT_SERVER_CMD:-cactus serve Cactus-Compute/gemma-4-E2B-it
51
61
  # ------------------------------------------------------------
52
62
  agent_config_save() {
53
63
  mkdir -p "$AGENT_CONF_DIR"
54
- cat >"$AGENT_CONF_FILE" <<EOF
55
- # karnel agent — persisted settings
56
- AGENT_MODEL="$AGENT_MODEL"
57
- AGENT_ENDPOINT="$AGENT_ENDPOINT"
58
- AGENT_TEMPERATURE=$AGENT_TEMPERATURE
59
- AGENT_MAX_TOKENS=$AGENT_MAX_TOKENS
60
- AGENT_MAX_ITERATIONS=$AGENT_MAX_ITERATIONS
61
- AGENT_CONFIRM_COMMANDS=$AGENT_CONFIRM_COMMANDS
62
- AGENT_CONTEXT_WINDOW=$AGENT_CONTEXT_WINDOW
63
- AGENT_SERVER_CMD="$AGENT_SERVER_CMD"
64
- EOF
64
+ {
65
+ printf 'AGENT_MODEL="%s"\n' "$AGENT_MODEL"
66
+ printf 'AGENT_ENDPOINT="%s"\n' "$AGENT_ENDPOINT"
67
+ printf 'AGENT_TEMPERATURE="%s"\n' "$AGENT_TEMPERATURE"
68
+ printf 'AGENT_MAX_TOKENS="%s"\n' "$AGENT_MAX_TOKENS"
69
+ printf 'AGENT_MAX_ITERATIONS="%s"\n' "$AGENT_MAX_ITERATIONS"
70
+ printf 'AGENT_CONFIRM_COMMANDS="%s"\n' "$AGENT_CONFIRM_COMMANDS"
71
+ printf 'AGENT_CONTEXT_WINDOW="%s"\n' "$AGENT_CONTEXT_WINDOW"
72
+ printf 'AGENT_SERVER_CMD="%s"\n' "$AGENT_SERVER_CMD"
73
+ if [[ "${KARNEL_AGENT_WS_SET:-0}" == "1" ]]; then
74
+ printf 'AGENT_WORKSPACE="%s"\n' "$AGENT_WORKSPACE"
75
+ fi
76
+ } >"$AGENT_CONF_FILE"
65
77
  }
66
78
 
67
79
  # ------------------------------------------------------------
@@ -73,13 +85,26 @@ agent_config_set() {
73
85
  case "$key" in
74
86
  model | --model | -m) AGENT_MODEL="$value" ;;
75
87
  endpoint | --endpoint | -u) AGENT_ENDPOINT="$value" ;;
76
- temp | temperature) AGENT_TEMPERATURE="$value" ;;
77
- max_tokens | maxtokens) AGENT_MAX_TOKENS="$value" ;;
78
- iterations | max_iterations) AGENT_MAX_ITERATIONS="$value" ;;
79
- confirm_commands | confirm) AGENT_CONFIRM_COMMANDS="$value" ;;
80
- context_window | context) AGENT_CONTEXT_WINDOW="$value" ;;
88
+ temp | temperature)
89
+ [[ "$value" =~ ^[0-9]+(\.[0-9]+)?$ ]] || { log_error "temperature must be a number (e.g. 0.3)"; return 1; }
90
+ AGENT_TEMPERATURE="$value" ;;
91
+ max_tokens | maxtokens)
92
+ [[ "$value" =~ ^[0-9]+$ ]] || { log_error "max_tokens must be a positive integer"; return 1; }
93
+ AGENT_MAX_TOKENS="$value" ;;
94
+ iterations | max_iterations)
95
+ [[ "$value" =~ ^[0-9]+$ ]] || { log_error "iterations must be a positive integer"; return 1; }
96
+ AGENT_MAX_ITERATIONS="$value" ;;
97
+ confirm_commands | confirm)
98
+ [[ "$value" =~ ^[01]$ ]] || { log_error "confirm_commands must be 0 or 1"; return 1; }
99
+ AGENT_CONFIRM_COMMANDS="$value" ;;
100
+ context_window | context)
101
+ [[ "$value" =~ ^[0-9]+$ ]] || { log_error "context_window must be a positive integer"; return 1; }
102
+ AGENT_CONTEXT_WINDOW="$value" ;;
81
103
  server_command | server) AGENT_SERVER_CMD="$value" ;;
82
- workspace) AGENT_WORKSPACE="$value" ;;
104
+ workspace)
105
+ [[ -n "$value" ]] || { log_error "workspace requires a path"; return 1; }
106
+ AGENT_WORKSPACE="$value"
107
+ KARNEL_AGENT_WS_SET=1 ;;
83
108
  *)
84
109
  log_error "Unknown setting: $key"
85
110
  echo
@@ -162,15 +187,26 @@ agent_models_list() {
162
187
  agent_build_payload() {
163
188
  local messages="$1"
164
189
  local extra="${2:-}"
165
- local payload
190
+ local payload temp mt
191
+ # Coerce numeric fields to valid JSON numbers; a bad/empty config
192
+ # value would otherwise make jq emit an empty payload and silently
193
+ # break every ask/run call.
194
+ temp="$AGENT_TEMPERATURE"
195
+ mt="$AGENT_MAX_TOKENS"
196
+ [[ "$temp" =~ ^[0-9]+(\.[0-9]+)?$ ]] || temp=0.3
197
+ [[ "$mt" =~ ^[0-9]+$ ]] || mt=2048
166
198
  payload=$(jq -nc \
167
199
  --arg model "$AGENT_MODEL" \
168
200
  --argjson msgs "$messages" \
169
- --argjson temp "$AGENT_TEMPERATURE" \
170
- --argjson mt "$AGENT_MAX_TOKENS" \
201
+ --argjson temp "$temp" \
202
+ --argjson mt "$mt" \
171
203
  '{model:$model, messages:$msgs, temperature:$temp, max_tokens:$mt}')
204
+ if [[ -z "$payload" ]]; then
205
+ log_error "Failed to build request payload (check agent config values)"
206
+ return 1
207
+ fi
172
208
  if [[ -n "$extra" ]]; then
173
- payload=$(echo "$payload" | jq -c "$extra")
209
+ payload=$(echo "$payload" | jq -c "$extra") || { log_error "Failed to apply payload extra"; return 1; }
174
210
  fi
175
211
  echo "$payload"
176
212
  }
@@ -14,8 +14,10 @@ import() {
14
14
  local base="${KARNEL_PATH}"
15
15
  local resolved="${1/@/$base}.sh"
16
16
  local canonical base_canonical
17
- canonical="$(realpath "$resolved" 2>/dev/null || readlink -f "$resolved" 2>/dev/null || echo "$resolved")"
18
- base_canonical="$(realpath "$base" 2>/dev/null || readlink -f "$base" 2>/dev/null || echo "$base")"
17
+ canonical="$(realpath "$resolved" 2>/dev/null || readlink -f "$resolved" 2>/dev/null)"
18
+ [[ -z "$canonical" ]] && { echo "karnel: import error: cannot resolve path: $1" >&2; return 1; }
19
+ base_canonical="$(realpath "$base" 2>/dev/null || readlink -f "$base" 2>/dev/null)"
20
+ [[ -z "$base_canonical" ]] && { echo "karnel: import error: cannot resolve KARNEL_PATH" >&2; return 1; }
19
21
 
20
22
  if [[ "$canonical" != "$base_canonical"/* ]]; then
21
23
  echo "karnel: import error: path traversal denied: $1" >&2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karnel-termux",
3
- "version": "4.16.6",
3
+ "version": "4.17.0",
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"
@@ -28,11 +28,21 @@ cpSync(packageRoot, stagingRoot, {
28
28
  return relative === "" || !relative.split(path.sep).some((part) => part === ".git" || part === "node_modules");
29
29
  },
30
30
  });
31
- if (!existsSync(releaseCommitPath)) {
32
- if (!/^[0-9a-f]{40}$/.test(repositoryHead)) {
33
- throw new Error("Required package file is missing: karnel/RELEASE_COMMIT");
34
- }
35
- writeFileSync(releaseCommitPath, `${repositoryHead}\n`, { flag: "wx", mode: 0o600 });
31
+ // Always bind the staged package to the actual repository HEAD. A stale
32
+ // committed karnel/RELEASE_COMMIT (left over from an earlier release) must
33
+ // never break release validation — the packed commit is what matters, and it
34
+ // must equal HEAD. When HEAD is unavailable (e.g. a non-git fixture) we fall
35
+ // back to a committed marker that is itself a valid SHA.
36
+ let committedCommit = "";
37
+ if (existsSync(releaseCommitPath)) {
38
+ committedCommit = readFileSync(releaseCommitPath, "utf8").trim();
39
+ }
40
+ if (/^[0-9a-f]{40}$/.test(repositoryHead)) {
41
+ writeFileSync(releaseCommitPath, `${repositoryHead}\n`, { mode: 0o600 });
42
+ } else if (/^[0-9a-f]{40}$/.test(committedCommit)) {
43
+ writeFileSync(releaseCommitPath, `${committedCommit}\n`, { mode: 0o600 });
44
+ } else {
45
+ throw new Error("Required package file is missing: karnel/RELEASE_COMMIT");
36
46
  }
37
47
  let output;
38
48
  try {
@@ -91,7 +91,26 @@ const main = () => {
91
91
  }
92
92
 
93
93
  try {
94
- const releaseCommit = fs.readFileSync(releaseCommitPath, 'utf8').trim();
94
+ let releaseCommit = '';
95
+ if (fs.existsSync(releaseCommitPath)) {
96
+ releaseCommit = fs.readFileSync(releaseCommitPath, 'utf8').trim();
97
+ }
98
+ if (!/^[0-9a-f]{40}$/.test(releaseCommit)) {
99
+ // The published tarball may not contain a baked RELEASE_COMMIT (e.g. a
100
+ // plain `npm publish`). Derive it from git so the installer's immutable
101
+ // commit-SHA requirement is still satisfied.
102
+ try {
103
+ releaseCommit = execSync('git rev-parse HEAD', {
104
+ cwd: path.resolve(__dirname, '..'),
105
+ encoding: 'utf8'
106
+ }).trim();
107
+ } catch {
108
+ releaseCommit = '';
109
+ }
110
+ if (/^[0-9a-f]{40}$/.test(releaseCommit)) {
111
+ fs.writeFileSync(releaseCommitPath, releaseCommit + '\n');
112
+ }
113
+ }
95
114
  if (!/^[0-9a-f]{40}$/.test(releaseCommit)) {
96
115
  throw new Error('package contains an invalid RELEASE_COMMIT');
97
116
  }