karnel-termux 4.13.11 → 4.14.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 (36) hide show
  1. package/README.md +7 -5
  2. package/docs/ARCHITECTURE.md +1 -1
  3. package/docs/CHANGELOG.md +12 -0
  4. package/docs/README.md +1 -1
  5. package/docs/cli/README.md +3 -3
  6. package/karnel/cli/commands/ia.sh +7 -7
  7. package/karnel/cli/commands/install.sh +7 -4
  8. package/karnel/cli/commands/list.sh +2 -0
  9. package/karnel/cli/commands/reinstall.sh +1 -185
  10. package/karnel/cli/karnel.sh +9 -45
  11. package/karnel/modules/ai.sh +9 -17
  12. package/karnel/tools/ai/all.sh +45 -11
  13. package/karnel/tools/ai/ampcode/install.sh +2 -2
  14. package/karnel/tools/ai/cactus/README.md +36 -0
  15. package/karnel/tools/ai/cactus/install.sh +171 -0
  16. package/karnel/tools/ai/codegraph/install.sh +1 -1
  17. package/karnel/tools/ai/codex/install.sh +1 -1
  18. package/karnel/tools/ai/command-code/install.sh +1 -1
  19. package/karnel/tools/ai/engram/install.sh +1 -1
  20. package/karnel/tools/ai/gemini-cli/install.sh +1 -1
  21. package/karnel/tools/ai/hermes-agent/install.sh +1 -1
  22. package/karnel/tools/ai/hugging-face/README.md +34 -0
  23. package/karnel/tools/ai/hugging-face/install.sh +129 -0
  24. package/karnel/tools/ai/kilocode-cli/install.sh +3 -3
  25. package/karnel/tools/ai/kimi-code/install.sh +1 -1
  26. package/karnel/tools/ai/minimax-cli/install.sh +1 -1
  27. package/karnel/tools/ai/mistral-vibe/install.sh +1 -1
  28. package/karnel/tools/ai/oh-my-pi/install.sh +2 -2
  29. package/karnel/tools/ai/ollama/install.sh +1 -1
  30. package/karnel/tools/ai/openclaude/install.sh +1 -1
  31. package/karnel/tools/ai/openclaw/install.sh +1 -1
  32. package/karnel/tools/ai/pi/install.sh +1 -1
  33. package/karnel/tools/ai/qoder/install.sh +2 -2
  34. package/karnel/tools/ai/qwen-code/install.sh +1 -1
  35. package/karnel/utils/tools.sh +8 -1
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://github.com/israelmarques1024-dotcom/karnel-termux">
11
- <img src="https://img.shields.io/badge/version-4.13.11-0078D4?style=for-the-badge" alt="Version">
11
+ <img src="https://img.shields.io/badge/version-4.14.0-0078D4?style=for-the-badge" alt="Version">
12
12
  </a>
13
13
  <a href="https://www.npmjs.com/package/karnel-termux">
14
14
  <img src="https://img.shields.io/npm/v/karnel-termux?style=for-the-badge&logo=npm&color=cb3837" alt="npm">
@@ -33,7 +33,7 @@
33
33
 
34
34
  Created by **Israel Marques**.
35
35
 
36
- - **41 AI agents** for coding — Claude, Gemini, OpenCode, KeelCode, Qoder, Ollama, Goose, Factory Droid, Cline, Puter, OmniRoute and more
36
+ - **43 AI tools** — Claude, Gemini, OpenCode, Cactus, Hugging Face, Ollama, KeelCode, Goose, Factory Droid and more
37
37
  - **8 languages** — Node.js, Python, Go, Rust, C/C++, PHP, Perl, Bun
38
38
  - **5 databases** — PostgreSQL, MariaDB, SQLite, MongoDB, Redis
39
39
  - **22 dev tools** — gh, curl, fzf, bat, lsd, jq, tmux, openssh, snyk and more
@@ -115,7 +115,7 @@ karnel
115
115
  |--------|-------------|--------------|
116
116
  | `lang` | Node.js, Python, Go, Rust, C/C++, PHP, Perl, Bun | `karnel install lang` |
117
117
  | `db` | PostgreSQL, MariaDB, SQLite, MongoDB, Redis | `karnel install db` |
118
- | `ai` | 41 AI agents for coding, including KeelCode | `karnel install ai` |
118
+ | `ai` | 43 AI tools, agents, and local inference clients | `karnel install ai` |
119
119
  | `editor` | code-server (VS Code in browser), Neovim, NvChad | `karnel install editor` |
120
120
  | `dev` | gh, curl, fzf, bat, lsd, jq and more | `karnel install dev` |
121
121
  | `npm` | TypeScript, NestJS CLI, Prettier and more | `karnel install npm` |
@@ -259,7 +259,7 @@ See the bundled reference with `karnel show osint --robin`.
259
259
 
260
260
  ---
261
261
 
262
- ## AI Agents (41)
262
+ ## AI Tools (43)
263
263
 
264
264
  ```bash
265
265
  karnel install ai # Install all
@@ -278,6 +278,8 @@ karnel install ai --opencode --ollama # Install specific agents
278
278
  | **OpenClaude** | `--openclaude` | Open source Claude Code alternative |
279
279
  | **OpenClaw** | `--openclaw` | Personal AI assistant |
280
280
  | **Ollama** | `--ollama` | Run open source LLMs locally |
281
+ | **Cactus** | `--cactus` | On-device ARM64 inference through an isolated Ubuntu runtime |
282
+ | **Hugging Face** | `--hugging-face` | Official Hugging Face Hub CLI (`hf`) |
281
283
  | **Codex CLI** | `--codex` | OpenAI coding agent |
282
284
  | **OpenCode** | `--opencode` | Open source terminal agent |
283
285
  | **MiMoCode** | `--mimocode` | Fast open source AI agent |
@@ -545,7 +547,7 @@ karnel/
545
547
  │ │ └── karnel.sh # Main CLI (with TUI)
546
548
  │ ├── modules/ # Module orchestrators
547
549
  │ ├── tools/ # Tool installers
548
- │ │ ├── ai/ # 41 AI agents
550
+ │ │ ├── ai/ # 43 AI tools
549
551
  │ │ ├── lang/ # 8 languages
550
552
  │ │ ├── db/ # 5 databases
551
553
  │ │ ├── dev/ # 22 dev tools
@@ -27,7 +27,7 @@ karnel-termux/
27
27
  │ │ ├── dev.sh # Dev tools installer
28
28
  │ │ └── ...
29
29
  │ ├── tools/ # Individual tool installers
30
- │ │ ├── ai/ # 41 AI agents
30
+ │ │ ├── ai/ # 43 AI tools
31
31
  │ │ ├── lang/ # 8 languages
32
32
  │ │ ├── db/ # 5 databases
33
33
  │ │ ├── dev/ # 22 dev tools
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Documentation Changelog
2
2
 
3
+ ## 4.14.0 - 2026-08-14
4
+
5
+ - Added tested Cactus 2.0.1 support on ARM64 through Ubuntu Proot with an
6
+ isolated compatible Python runtime.
7
+ - Added the official Hugging Face `hf` CLI in an isolated native Termux
8
+ environment, using HTTP transport when the optional Xet Android wheel is
9
+ unavailable.
10
+ - Made the AI registry authoritative for TUI selection, route discovery, and
11
+ lifecycle dispatch, with contracts covering all 43 registered tools.
12
+ - Fixed AI reinstall and uninstall error propagation, unknown status handling,
13
+ binary alias summaries, ownership checks, and stale tool counts.
14
+
3
15
  ## 4.13.11 - 2026-08-10
4
16
 
5
17
  - Hardened installers to preserve user-managed commands and data, with atomic
package/docs/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
  ## What is Karnel?
15
15
 
16
16
  Karnel Termux transforms your Android device into a complete development workstation.
17
- With a single CLI (`karnel`), you can install and manage 41 AI agents, 8 languages,
17
+ With a single CLI (`karnel`), you can install and manage 43 AI tools, 8 languages,
18
18
  5 databases, 22 dev tools, 12 utility tools, 2 network tools, 6 games, responsible OSINT, and more.
19
19
 
20
20
  ## Sections
@@ -22,7 +22,7 @@ karnel install <module> --tool1 --tool2 # Install specific tools only
22
22
  |-----------|-------------|
23
23
  | `lang` | Node.js, Python, Go, Rust, C/C++, PHP, Perl, Bun |
24
24
  | `db` | PostgreSQL, MariaDB, SQLite, MongoDB, Redis |
25
- | `ai` | 41 AI agents (OpenCode, Claude Code, KeelCode, Codebuff, Freebuff, Qoder, AMP Code, Cursor CLI, Oh-My-Pi, Goose, Factory Droid, Puter, Ollama, etc.) |
25
+ | `ai` | 43 AI tools (OpenCode, Cactus, Hugging Face, Claude Code, KeelCode, Goose, Factory Droid, Ollama, etc.) |
26
26
  | `editor` | code-server (VS Code in browser), Neovim, NvChad |
27
27
  | `dev` | gh, curl, fzf, bat, lsd, jq, tmux, openssh, snyk (22 tools) |
28
28
  | `npm` | TypeScript, NestJS CLI, Prettier, Vercel CLI, etc. |
@@ -42,7 +42,7 @@ karnel install <module> --tool1 --tool2 # Install specific tools only
42
42
 
43
43
  | Module | Flags |
44
44
  |-----------|-------|
45
- | `ai` | `--qwen-code`, `--gemini-cli`, `--claude-code`, `--mistral-vibe`, `--openclaude`, `--openclaw`, `--ollama`, `--codex`, `--opencode`, `--mimocode`, `--engram`, `--codegraph`, `--pi`, `--antigravity-cli`, `--minimax-cli`, `--gentle-ai`, `--gga`, `--hermes-agent`, `--kimi-code`, `--command-code`, `--codebuff`, `--freebuff`, `--kilocode-cli`, `--kiro`, `--crush`, `--cline`, `--odysseus`, `--kimchi-code`, `--omni-route`, `--ctx7`, `--openspec`, `--supercode-cli`, `--puter`, `--keelcode`, `--copilot-termux`, `--qoder`, `--ampcode`, `--cursor-cli`, `--oh-my-pi`, `--goose`, `--droid` |
45
+ | `ai` | `--qwen-code`, `--gemini-cli`, `--claude-code`, `--mistral-vibe`, `--openclaude`, `--openclaw`, `--ollama`, `--codex`, `--opencode`, `--mimocode`, `--engram`, `--codegraph`, `--pi`, `--antigravity-cli`, `--minimax-cli`, `--gentle-ai`, `--gga`, `--hermes-agent`, `--kimi-code`, `--command-code`, `--codebuff`, `--freebuff`, `--kilocode-cli`, `--kiro`, `--crush`, `--cline`, `--odysseus`, `--kimchi-code`, `--omni-route`, `--ctx7`, `--openspec`, `--supercode-cli`, `--puter`, `--keelcode`, `--copilot-termux`, `--qoder`, `--ampcode`, `--cursor-cli`, `--oh-my-pi`, `--goose`, `--droid`, `--cactus`, `--hugging-face` |
46
46
  | `db` | `--postgresql`, `--mariadb`, `--sqlite`, `--mongodb`, `--redis` |
47
47
  | `dev` | `--gh`, `--wget`, `--curl`, `--lsd`, `--bat`, `--proot`, `--ncurses`, `--tmate`, `--openssh`, `--tmux`, `--cloudflared`, `--translate`, `--html2text`, `--jq`, `--bc`, `--tree`, `--fzf`, `--imagemagick`, `--shfmt`, `--make`, `--udocker`, `--snyk` |
48
48
  | `lang` | `--bun`, `--nodejs`, `--python`, `--perl`, `--php`, `--rust`, `--clang`, `--golang` |
@@ -136,7 +136,7 @@ binary/command name, and current install status.
136
136
  |-----------|-------|
137
137
  | `lang` | 8 languages |
138
138
  | `db` | 5 databases |
139
- | `ai` | 41 AI agents |
139
+ | `ai` | 43 AI tools |
140
140
  | `editor` | 3 editor components |
141
141
  | `dev` | 22 development tools |
142
142
  | `npm` | 11 npm global modules |
@@ -441,8 +441,12 @@ ia_routes() {
441
441
 
442
442
  local -a routes=()
443
443
 
444
- # Collect all available AI CLIs (em sincronia com AI_TOOLS_REGISTRY em tools/ai/all.sh)
445
- for cmd in qwen gemini claude vibe openclaude openclaw ollama codex opencode mimo engram codegraph pi agy mmx gentle-ai gga hermes kimi command-code codebuff freebuff kilocode kiro cline crush odysseus kimchi omni-route ctx7 openspec copilot amp cursor omp goose droid supercode puter; do
444
+ import "@/tools/ai/all"
445
+ local entry binaries cmd
446
+ for entry in "${AI_TOOLS_REGISTRY[@]}"; do
447
+ binaries="${entry##*:}"
448
+ IFS=',' read -ra route_commands <<< "$binaries"
449
+ for cmd in "${route_commands[@]}"; do
446
450
  if command -v "$cmd" &>/dev/null; then
447
451
  local path
448
452
  path=$(command -v "$cmd")
@@ -450,13 +454,9 @@ ia_routes() {
450
454
  else
451
455
  routes+=("$cmd|not installed|missing")
452
456
  fi
457
+ done
453
458
  done
454
459
 
455
- if [[ ${#routes[@]} -eq 0 ]]; then
456
- log_warn "No AI CLIs found"
457
- return 0
458
- fi
459
-
460
460
  table_start "CLI" "Path" "Status"
461
461
  for route in "${routes[@]}"; do
462
462
  local cli
@@ -188,9 +188,7 @@ _install_specific_tools() {
188
188
  local failed_count=0
189
189
  local skipped_count=0
190
190
 
191
- # These entries MUST stay in sync with AI_TOOLS_REGISTRY in karnel/tools/ai/all.sh
192
- # To add a new tool: 1) create install.sh 2) add to AI_TOOLS_REGISTRY 3) add case here
193
- # Import the registry for _validate_tool_installed
191
+ # The registry is authoritative for accepted flags and post-install binaries.
194
192
  local -A _tool_binaries=()
195
193
  local _entry _id _name _bins
196
194
  for _entry in "${AI_TOOLS_REGISTRY[@]}"; do
@@ -200,7 +198,12 @@ _install_specific_tools() {
200
198
 
201
199
  for tool in "${tools[@]}"; do
202
200
  local func_name="install_${tool//-/_}"
203
- local tool_display="${tool//-/_}"
201
+
202
+ if [[ -z "${_tool_binaries[$tool]+registered}" ]]; then
203
+ log_warn "Unknown AI tool: --$tool"
204
+ ((failed_count++))
205
+ continue
206
+ fi
204
207
 
205
208
  if declare -f "$func_name" &>/dev/null; then
206
209
  "$func_name"
@@ -206,6 +206,8 @@ _list_ai() {
206
206
  table_row "Oh-My-Pi" "--oh-my-pi" "omp" "$(_check_cmd "omp")"
207
207
  table_row "Goose CLI" "--goose" "goose" "$(_check_cmd "goose")"
208
208
  table_row "Factory Droid" "--droid" "droid" "$(_check_cmd "droid")"
209
+ table_row "Cactus" "--cactus" "cactus" "$(_check_cmd "cactus")"
210
+ table_row "Hugging Face" "--hugging-face" "hf" "$(_check_cmd "hf")"
209
211
  table_row "Copilot-Termux" "--copilot-termux" "copilot" "$(_check_cmd "copilot")"
210
212
  table_row "Supercode CLI" "--supercode-cli" "supercode" "$(_check_cmd "supercode")"
211
213
  table_row "Puter CLI" "--puter" "puter" "$(_check_cmd "puter")"
@@ -182,191 +182,7 @@ _reinstall_specific_tools() {
182
182
 
183
183
  case "$module" in
184
184
  ai)
185
- import "@/tools/ai/all"
186
- local reinstalled_count=0
187
- local failed_count=0
188
-
189
- for tool in "${tools[@]}"; do
190
- case "$tool" in
191
- qwen-code)
192
- reinstall_qwen_code
193
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
194
- ;;
195
- gemini-cli)
196
- reinstall_gemini_cli
197
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
198
- ;;
199
- claude-code)
200
- reinstall_claude_code
201
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
202
- ;;
203
- mistral-vibe)
204
- reinstall_mistral_vibe
205
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
206
- ;;
207
- openclaude)
208
- reinstall_openclaude
209
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
210
- ;;
211
- openclaw)
212
- reinstall_openclaw
213
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
214
- ;;
215
- ollama)
216
- reinstall_ollama
217
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
218
- ;;
219
- codex)
220
- reinstall_codex
221
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
222
- ;;
223
- opencode)
224
- reinstall_opencode
225
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
226
- ;;
227
- kilocode-cli)
228
- reinstall_kilocode_cli
229
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
230
- ;;
231
- kimchi-code)
232
- reinstall_kimchi_code
233
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
234
- ;;
235
- mimocode)
236
- reinstall_mimocode
237
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
238
- ;;
239
- engram)
240
- reinstall_engram
241
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
242
- ;;
243
- codegraph)
244
- reinstall_codegraph
245
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
246
- ;;
247
- pi)
248
- reinstall_pi
249
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
250
- ;;
251
- antigravity-cli)
252
- reinstall_antigravity_cli
253
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
254
- ;;
255
- minimax-cli)
256
- reinstall_minimax_cli
257
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
258
- ;;
259
- gentle-ai)
260
- reinstall_gentle_ai
261
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
262
- ;;
263
- gga)
264
- reinstall_gga
265
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
266
- ;;
267
- hermes-agent)
268
- reinstall_hermes_agent
269
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
270
- ;;
271
- kimi-code)
272
- reinstall_kimi_code
273
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
274
- ;;
275
- command-code)
276
- reinstall_command_code
277
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
278
- ;;
279
- codebuff)
280
- reinstall_codebuff
281
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
282
- ;;
283
- freebuff)
284
- reinstall_freebuff
285
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
286
- ;;
287
- ctx7)
288
- reinstall_ctx7
289
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
290
- ;;
291
- openspec)
292
- reinstall_openspec
293
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
294
- ;;
295
- supercode-cli)
296
- reinstall_supercode_cli
297
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
298
- ;;
299
- puter)
300
- reinstall_puter
301
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
302
- ;;
303
- keelcode)
304
- reinstall_keelcode
305
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
306
- ;;
307
- qoder)
308
- reinstall_qoder
309
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
310
- ;;
311
- ampcode)
312
- reinstall_ampcode
313
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
314
- ;;
315
- cursor-cli)
316
- reinstall_cursor_cli
317
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
318
- ;;
319
- oh-my-pi)
320
- reinstall_oh_my_pi
321
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
322
- ;;
323
- goose)
324
- reinstall_goose
325
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
326
- ;;
327
- droid)
328
- reinstall_droid
329
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
330
- ;;
331
- copilot-termux)
332
- reinstall_copilot_termux
333
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
334
- ;;
335
- kiro)
336
- reinstall_kiro
337
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
338
- ;;
339
- crush)
340
- reinstall_crush
341
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
342
- ;;
343
- cline)
344
- reinstall_cline
345
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
346
- ;;
347
- odysseus)
348
- reinstall_odysseus
349
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
350
- ;;
351
- omni-route)
352
- reinstall_omni_route
353
- case $? in 0) ((reinstalled_count++));; 1) ((failed_count++));; esac
354
- ;;
355
- *)
356
- log_warn "Unknown AI tool: --$tool"
357
- ((failed_count++))
358
- ;;
359
- esac
360
- done
361
-
362
- echo
363
- if [[ $reinstalled_count -gt 0 ]]; then
364
- log_success "$reinstalled_count AI tool(s) reinstalled"
365
- fi
366
- if [[ $failed_count -gt 0 ]]; then
367
- log_warn "$failed_count tool(s) failed to reinstall"
368
- fi
369
- echo
185
+ _batch_tool_action "ai" "reinstall" "${tools[@]}" || return 1
370
186
  ;;
371
187
  db)
372
188
  import "@/tools/db/all"
@@ -110,7 +110,7 @@ karnel_help() {
110
110
  echo
111
111
  log_info "Use with: karnel install|update|reinstall|uninstall <target> [--tool...]"
112
112
  echo
113
- printf " ${D_GREEN}%-10s${NC} %s\n" "ai" "41 AI tools (OpenCode, Claude, KeelCode, Ollama, Copilot-Termux, Goose, etc.)"
113
+ printf " ${D_GREEN}%-10s${NC} %s\n" "ai" "43 AI tools (OpenCode, Cactus, Hugging Face, Claude, Ollama, Goose, etc.)"
114
114
  printf " ${D_GREEN}%-10s${NC} %s\n" "auto" "Automation (n8n)"
115
115
  printf " ${D_GREEN}%-10s${NC} %s\n" "db" "PostgreSQL, MariaDB, SQLite, MongoDB, Redis"
116
116
  printf " ${D_GREEN}%-10s${NC} %s\n" "deploy" "Vercel, Railway, Netlify, Supabase CLIs"
@@ -370,7 +370,7 @@ _tui_list_menu() {
370
370
  local modules="ai db lang dev editor npm shell ui auto deploy games network utils osint voice plugin security"
371
371
  local choice
372
372
  choice=$(_dialog_menu "List Tools" "Select a module to list:" \
373
- "ai" "AI Agents (41)" \
373
+ "ai" "AI Tools (43)" \
374
374
  "db" "Databases" \
375
375
  "lang" "Programming Languages" \
376
376
  "editor" "Code Editors" \
@@ -760,49 +760,13 @@ _tui_install_checklist() {
760
760
  )
761
761
  ;;
762
762
  ai)
763
- opts=(
764
- "qwen-code" "Qwen Code" OFF
765
- "gemini-cli" "Gemini CLI" OFF
766
- "claude-code" "Claude Code" OFF
767
- "mistral-vibe" "Mistral Vibe" OFF
768
- "openclaude" "OpenClaude" OFF
769
- "openclaw" "OpenClaw" OFF
770
- "ollama" "Ollama" OFF
771
- "codex" "Codex CLI" OFF
772
- "opencode" "OpenCode" OFF
773
- "mimocode" "MiMo Code" OFF
774
- "engram" "Engram" OFF
775
- "codegraph" "CodeGraph" OFF
776
- "pi" "Pi" OFF
777
- "antigravity-cli" "Antigravity CLI" OFF
778
- "gentle-ai" "Gentle AI" OFF
779
- "minimax-cli" "Minimax CLI" OFF
780
- "gga" "GGA" OFF
781
- "hermes-agent" "Hermes Agent" OFF
782
- "kimi-code" "Kimi Code" OFF
783
- "command-code" "Command Code" OFF
784
- "codebuff" "Codebuff" OFF
785
- "freebuff" "Freebuff" OFF
786
- "crush" "Crush CLI" OFF
787
- "odysseus" "Odysseus" OFF
788
- "kilocode-cli" "Kilo Code CLI" OFF
789
- "kiro" "Kiro CLI" OFF
790
- "cline" "Cline CLI" OFF
791
- "kimchi-code" "Kimchi CLI" OFF
792
- "omni-route" "omniRoute" OFF
793
- "ctx7" "Context7 Docs" OFF
794
- "openspec" "OpenSpec SDD" OFF
795
- "supercode-cli" "Supercode CLI" OFF
796
- "puter" "Puter CLI" OFF
797
- "keelcode" "KeelCode" OFF
798
- "copilot-termux" "Copilot-Termux" OFF
799
- "qoder" "Qoder" OFF
800
- "ampcode" "AMP Code CLI" OFF
801
- "cursor-cli" "Cursor CLI" OFF
802
- "oh-my-pi" "Oh-My-Pi" OFF
803
- "goose" "Goose CLI" OFF
804
- "droid" "Factory Droid" OFF
805
- )
763
+ import "@/tools/ai/all"
764
+ opts=()
765
+ local entry id name binaries
766
+ for entry in "${AI_TOOLS_REGISTRY[@]}"; do
767
+ IFS=':' read -r id name binaries <<< "$entry"
768
+ opts+=("$id" "$name" OFF)
769
+ done
806
770
  ;;
807
771
  dev)
808
772
  opts=(
@@ -38,14 +38,17 @@ install_ai() {
38
38
  echo
39
39
 
40
40
  # Mostra apenas ferramentas que foram realmente instaladas (command -v)
41
- local _reg_entry _id _name _bins
41
+ local _reg_entry _id _name _bins _bin
42
+ local -a _registered_bins
42
43
  for _reg_entry in "${AI_TOOLS_REGISTRY[@]}"; do
43
44
  IFS=':' read -r _id _name _bins <<< "$_reg_entry"
44
- # Pega o primeiro binario da lista (separado por virgula)
45
- local _first_bin="${_bins%%,*}"
46
- if command -v "$_first_bin" &>/dev/null; then
47
- list_item "$_name ${GRAY}(${D_GREEN}$_first_bin${GRAY})"
48
- fi
45
+ IFS=',' read -ra _registered_bins <<< "$_bins"
46
+ for _bin in "${_registered_bins[@]}"; do
47
+ if command -v "$_bin" &>/dev/null; then
48
+ list_item "$_name ${GRAY}(${D_GREEN}$_bin${GRAY})"
49
+ break
50
+ fi
51
+ done
49
52
  done
50
53
 
51
54
  echo
@@ -117,17 +120,6 @@ reinstall_ai() {
117
120
  local rc=0
118
121
  import "@/tools/ai/all"
119
122
  reinstall_all_ai_tools || rc=$?
120
- echo
121
- log_info "Reinstalled tools:"
122
- echo
123
-
124
- local _reg_entry _id _name _bins
125
- for _reg_entry in "${AI_TOOLS_REGISTRY[@]}"; do
126
- IFS=':' read -r _id _name _bins <<< "$_reg_entry"
127
- list_item "$_name"
128
- done
129
- echo
130
-
131
123
  if [ "$rc" -eq 0 ]; then
132
124
  log_success "AI tools reinstalled successfully"
133
125
  else
@@ -69,6 +69,8 @@ AI_TOOLS_REGISTRY=(
69
69
  "oh-my-pi:Oh-My-Pi:omp"
70
70
  "goose:Goose CLI:goose"
71
71
  "droid:Factory Droid:droid"
72
+ "cactus:Cactus:cactus"
73
+ "hugging-face:Hugging Face:hf"
72
74
  )
73
75
 
74
76
  # ---- IMPORTAR TODOS OS SCRIPTS INDIVIDUAIS ----
@@ -115,6 +117,41 @@ _validate_tool_installed() {
115
117
 
116
118
  # ---- FUNÇÕES DE LOTE ----
117
119
 
120
+ _ai_tool_registered() {
121
+ local wanted="$1"
122
+ local entry id
123
+ for entry in "${AI_TOOLS_REGISTRY[@]}"; do
124
+ id="${entry%%:*}"
125
+ [[ "$id" == "$wanted" ]] && return 0
126
+ done
127
+ return 1
128
+ }
129
+
130
+ _run_ai_tool_action() {
131
+ local action="$1"
132
+ local id="$2"
133
+ local func_name="${action}_${id//-/_}"
134
+
135
+ _ai_tool_registered "$id" || return 127
136
+
137
+ if [[ "$action" != "reinstall" ]]; then
138
+ declare -f "$func_name" &>/dev/null || return 127
139
+ "$func_name"
140
+ return $?
141
+ fi
142
+
143
+ local uninstall_fn="uninstall_${id//-/_}"
144
+ local install_fn="install_${id//-/_}"
145
+ declare -f "$uninstall_fn" &>/dev/null && declare -f "$install_fn" &>/dev/null || return 127
146
+
147
+ "$uninstall_fn"
148
+ local uninstall_rc=$?
149
+ if [[ $uninstall_rc -ne 0 && $uninstall_rc -ne 2 ]]; then
150
+ return "$uninstall_rc"
151
+ fi
152
+ "$install_fn"
153
+ }
154
+
118
155
  _all_ai_tools_action() {
119
156
  local action="$1" # install, uninstall, update, reinstall
120
157
  local action_label="$2" # Instalando, Removendo, Atualizando, Reinstalando
@@ -128,15 +165,12 @@ _all_ai_tools_action() {
128
165
 
129
166
  for entry in "${AI_TOOLS_REGISTRY[@]}"; do
130
167
  IFS=':' read -r id name binaries <<< "$entry"
131
- local func_name="${action}_${id//-/_}"
132
-
133
168
  ((current++))
134
169
 
135
- if declare -f "$func_name" &>/dev/null; then
136
- "$func_name"
137
- local rc=$?
170
+ _run_ai_tool_action "$action" "$id"
171
+ local rc=$?
138
172
 
139
- case $rc in
173
+ case $rc in
140
174
  0)
141
175
  # Pós-validação apenas para install
142
176
  if [[ "$action" == "install" ]]; then
@@ -161,11 +195,11 @@ _all_ai_tools_action() {
161
195
  2)
162
196
  ((skipped_count++))
163
197
  ;;
164
- esac
165
- else
166
- log_warn "Função não encontrada: $func_name"
167
- ((failed_count++))
168
- fi
198
+ *)
199
+ log_warn "$name: ${action}_${id//-/_} returned status $rc"
200
+ ((failed_count++))
201
+ ;;
202
+ esac
169
203
 
170
204
  progress_update "$current" "$total"
171
205
  done
@@ -214,9 +214,9 @@ uninstall_ampcode() {
214
214
  mkdir -p "$(dirname "$LOG_FILE")"
215
215
  if [ ! -f "$PREFIX/bin/amp" ]; then
216
216
  log_warn "AMP Code CLI is not installed"
217
- return 1
217
+ return 2
218
218
  fi
219
- loading "Uninstalling AMP Code CLI" _uninstall_ampcode_impl
219
+ loading "Uninstalling AMP Code CLI" _uninstall_ampcode_impl || return 1
220
220
  }
221
221
 
222
222
  _uninstall_ampcode_impl() {
@@ -0,0 +1,36 @@
1
+ # Cactus
2
+
3
+ Cactus is an on-device inference engine for text, vision, speech, embeddings,
4
+ and RAG. Karnel installs the official `cactus-compute` 2.0.1 ARM64 package in
5
+ an Ubuntu `proot-distro` environment because its Linux wheel targets glibc,
6
+ while native Termux uses Android/Bionic.
7
+
8
+ If the installed Ubuntu container uses Python 3.14 or newer, Karnel provisions
9
+ an isolated upstream-compatible Python 3.13 runtime with `uv`; it does not
10
+ replace the container's system Python.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ karnel install ai --cactus
16
+ cactus --help
17
+ cactus run Cactus-Compute/needle
18
+ ```
19
+
20
+ Other examples:
21
+
22
+ ```bash
23
+ cactus download Cactus-Compute/needle
24
+ cactus list
25
+ cactus serve Cactus-Compute/needle
26
+ ```
27
+
28
+ The first model download can be large and inference performance depends on the
29
+ device's RAM, storage, and thermal limits. Cactus is currently enabled only on
30
+ ARM64 because that is the Linux wheel published by the upstream project.
31
+
32
+ Karnel removes only its managed Python environment and wrapper. Model data
33
+ created elsewhere is preserved. Cactus uses a source-available license with
34
+ commercial-use restrictions; review the upstream license before use.
35
+
36
+ Official project: <https://github.com/cactus-compute/cactus>
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env bash
2
+
3
+ import "@/utils/log"
4
+
5
+ : "${KARNEL_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
6
+ CACTUS_VERSION="2.0.1"
7
+ CACTUS_DATA_DIR="$KARNEL_DATA/cactus"
8
+ CACTUS_CONTAINER_DIR="/opt/karnel/cactus"
9
+ CACTUS_WRAPPER="$PREFIX/bin/cactus"
10
+
11
+ _cactus_data_owned() {
12
+ [[ -f "$CACTUS_DATA_DIR/.karnel-managed" ]]
13
+ }
14
+
15
+ _cactus_wrapper_owned() {
16
+ local marker="$CACTUS_DATA_DIR/.karnel-wrapper-cactus"
17
+ [[ -f "$marker" && -f "$CACTUS_WRAPPER" ]] || return 1
18
+ [[ "$(sha256sum "$CACTUS_WRAPPER" 2>/dev/null)" == "$(<"$marker")" ]]
19
+ }
20
+
21
+ _cactus_proot() {
22
+ proot-distro login --shared-tmp ubuntu -- "$@"
23
+ }
24
+
25
+ _cactus_container_owned() {
26
+ command -v proot-distro &>/dev/null || return 1
27
+ _cactus_proot test -f "$CACTUS_CONTAINER_DIR/.karnel-managed" &>/dev/null
28
+ }
29
+
30
+ _cactus_verify_ownership() {
31
+ if [[ -e "$CACTUS_DATA_DIR" ]] && ! _cactus_data_owned; then
32
+ log_error "Refusing to replace unowned Cactus data: $CACTUS_DATA_DIR"
33
+ return 1
34
+ fi
35
+ if [[ -e "$CACTUS_WRAPPER" ]] && ! _cactus_wrapper_owned; then
36
+ log_error "Refusing to replace unowned command: $CACTUS_WRAPPER"
37
+ return 1
38
+ fi
39
+ if command -v cactus &>/dev/null && ! _cactus_wrapper_owned; then
40
+ log_error "Refusing to shadow the existing cactus command: $(command -v cactus)"
41
+ return 1
42
+ fi
43
+ if command -v proot-distro &>/dev/null &&
44
+ _cactus_proot test -e "$CACTUS_CONTAINER_DIR" &>/dev/null &&
45
+ ! _cactus_container_owned; then
46
+ log_error "Refusing to replace unowned Cactus data in Ubuntu: $CACTUS_CONTAINER_DIR"
47
+ return 1
48
+ fi
49
+ }
50
+
51
+ _cactus_ensure_ubuntu() {
52
+ case "$(uname -m)" in
53
+ aarch64|arm64) ;;
54
+ *)
55
+ log_error "Cactus currently provides a Linux wheel only for ARM64"
56
+ return 1
57
+ ;;
58
+ esac
59
+ if ! command -v proot-distro &>/dev/null; then
60
+ pkg install proot-distro -y || return 1
61
+ fi
62
+ if ! proot-distro login ubuntu -- true &>/dev/null; then
63
+ proot-distro install ubuntu:24.04 || return 1
64
+ fi
65
+ }
66
+
67
+ _cactus_validate() {
68
+ _cactus_proot "$CACTUS_CONTAINER_DIR/venv/bin/cactus" --help &>/dev/null
69
+ }
70
+
71
+ _install_cactus_inside_ubuntu() {
72
+ _cactus_proot /bin/bash -c '
73
+ set -e
74
+ export DEBIAN_FRONTEND=noninteractive
75
+ if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
76
+ official_source=/etc/apt/sources.list.d/ubuntu.sources
77
+ else
78
+ official_source=/etc/apt/sources.list
79
+ fi
80
+ apt_options=(-o "Dir::Etc::sourcelist=$official_source" -o "Dir::Etc::sourceparts=-")
81
+ apt-get "${apt_options[@]}" update -qq
82
+ apt-get "${apt_options[@]}" install -y -qq ca-certificates python3 python3-pip python3-venv
83
+ if [ -e /opt/karnel/cactus ] && [ ! -f /opt/karnel/cactus/.karnel-managed ]; then
84
+ echo "Refusing to replace unowned /opt/karnel/cactus" >&2
85
+ exit 1
86
+ fi
87
+ rm -rf /opt/karnel/cactus
88
+ mkdir -p /opt/karnel/cactus
89
+ : > /opt/karnel/cactus/.karnel-managed
90
+ if python3 -c "import sys; raise SystemExit(sys.version_info >= (3, 14))"; then
91
+ python3 -m venv /opt/karnel/cactus/venv
92
+ else
93
+ python3 -m venv /opt/karnel/cactus/bootstrap
94
+ /opt/karnel/cactus/bootstrap/bin/python -m pip install --upgrade uv
95
+ export UV_PYTHON_INSTALL_DIR=/opt/karnel/cactus/python
96
+ /opt/karnel/cactus/bootstrap/bin/uv python install 3.13
97
+ /opt/karnel/cactus/bootstrap/bin/uv venv --seed --python 3.13 /opt/karnel/cactus/venv
98
+ fi
99
+ /opt/karnel/cactus/venv/bin/python -m pip install --upgrade "cactus-compute==2.0.1"
100
+ /opt/karnel/cactus/venv/bin/cactus --help >/dev/null
101
+ '
102
+ }
103
+
104
+ _cactus_write_wrapper() {
105
+ mkdir -p "$CACTUS_DATA_DIR" "$PREFIX/bin" || return 1
106
+ : >"$CACTUS_DATA_DIR/.karnel-managed" || return 1
107
+ local temporary
108
+ temporary="$(mktemp "$PREFIX/bin/.cactus.XXXXXX")" || return 1
109
+ cat >"$temporary" <<EOF
110
+ #!$PREFIX/bin/bash
111
+ # Karnel-managed Cactus wrapper
112
+ exec proot-distro login --shared-tmp ubuntu -- $CACTUS_CONTAINER_DIR/venv/bin/cactus "\$@"
113
+ EOF
114
+ chmod 755 "$temporary" || { rm -f "$temporary"; return 1; }
115
+ mv -f "$temporary" "$CACTUS_WRAPPER" || return 1
116
+ sha256sum "$CACTUS_WRAPPER" >"$CACTUS_DATA_DIR/.karnel-wrapper-cactus" || return 1
117
+ }
118
+
119
+ install_cactus() {
120
+ if _cactus_data_owned && _cactus_wrapper_owned && _cactus_container_owned && _cactus_validate; then
121
+ log_info "Cactus is already installed"
122
+ return 2
123
+ fi
124
+ _cactus_verify_ownership || return 1
125
+
126
+ log_info "Installing Cactus $CACTUS_VERSION in Ubuntu (glibc compatibility)..."
127
+ _cactus_ensure_ubuntu || return 1
128
+ if ! _install_cactus_inside_ubuntu || ! _cactus_write_wrapper || ! _cactus_validate; then
129
+ _cactus_wrapper_owned && rm -f "$CACTUS_WRAPPER"
130
+ _cactus_data_owned && rm -rf "$CACTUS_DATA_DIR"
131
+ log_error "Failed to install Cactus"
132
+ return 1
133
+ fi
134
+ log_success "Cactus installed"
135
+ log_info "Start with: cactus run Cactus-Compute/needle"
136
+ }
137
+
138
+ uninstall_cactus() {
139
+ if ! _cactus_data_owned && ! _cactus_wrapper_owned && ! _cactus_container_owned; then
140
+ if [[ -e "$CACTUS_DATA_DIR" || -e "$CACTUS_WRAPPER" ]]; then
141
+ _cactus_verify_ownership
142
+ return $?
143
+ fi
144
+ log_info "Cactus is not installed by Karnel"
145
+ return 2
146
+ fi
147
+ _cactus_verify_ownership || return 1
148
+
149
+ _cactus_wrapper_owned && rm -f "$CACTUS_WRAPPER"
150
+ if _cactus_container_owned; then
151
+ _cactus_proot rm -rf "$CACTUS_CONTAINER_DIR" || return 1
152
+ fi
153
+ _cactus_data_owned && rm -rf "$CACTUS_DATA_DIR"
154
+ log_success "Cactus uninstalled; downloaded model data outside its environment was preserved"
155
+ }
156
+
157
+ update_cactus() {
158
+ _cactus_data_owned && _cactus_wrapper_owned && _cactus_container_owned || {
159
+ log_error "Cactus is not installed by Karnel"
160
+ return 1
161
+ }
162
+ _cactus_verify_ownership || return 1
163
+ _cactus_proot "$CACTUS_CONTAINER_DIR/venv/bin/python" -m pip install --upgrade 'cactus-compute>=2,<3' || return 1
164
+ _cactus_validate || return 1
165
+ log_success "Cactus updated"
166
+ }
167
+
168
+ reinstall_cactus() {
169
+ uninstall_cactus || [[ $? -eq 2 ]] || return 1
170
+ install_cactus
171
+ }
@@ -142,7 +142,7 @@ uninstall_codegraph() {
142
142
  log_info "Uninstalling CodeGraph..."
143
143
  mkdir -p "$(dirname "$LOG_FILE")"
144
144
 
145
- loading "Removing CodeGraph" _uninstall_codegraph_impl
145
+ loading "Removing CodeGraph" _uninstall_codegraph_impl || return 1
146
146
 
147
147
  log_success "CodeGraph uninstalled"
148
148
  return 0
@@ -67,7 +67,7 @@ uninstall_codex() {
67
67
  log_info "Uninstalling Codex CLI..."
68
68
  mkdir -p "$(dirname "$LOG_FILE")"
69
69
 
70
- loading "Removing Codex CLI" _uninstall_codex_impl
70
+ loading "Removing Codex CLI" _uninstall_codex_impl || return 1
71
71
 
72
72
  log_success "Codex CLI uninstalled"
73
73
  return 0
@@ -122,7 +122,7 @@ uninstall_command_code() {
122
122
  log_info "Uninstalling Command Code..."
123
123
  mkdir -p "$(dirname "$LOG_FILE")"
124
124
 
125
- loading "Removing Command Code files" _uninstall_command_code_impl
125
+ loading "Removing Command Code files" _uninstall_command_code_impl || return 1
126
126
 
127
127
  log_success "Command Code uninstalled"
128
128
  return 0
@@ -85,7 +85,7 @@ uninstall_engram() {
85
85
  log_info "Uninstalling Engram..."
86
86
  mkdir -p "$(dirname "$LOG_FILE")"
87
87
 
88
- loading "Removing Engram" _uninstall_engram_impl
88
+ loading "Removing Engram" _uninstall_engram_impl || return 1
89
89
 
90
90
  log_success "Engram uninstalled"
91
91
  return 0
@@ -71,7 +71,7 @@ uninstall_gemini_cli() {
71
71
  log_info "Uninstalling Gemini CLI..."
72
72
  mkdir -p "$(dirname "$LOG_FILE")"
73
73
 
74
- loading "Removing Gemini CLI" _uninstall_gemini_cli_impl
74
+ loading "Removing Gemini CLI" _uninstall_gemini_cli_impl || return 1
75
75
 
76
76
  log_success "Gemini CLI uninstalled"
77
77
  return 0
@@ -86,7 +86,7 @@ uninstall_hermes_agent() {
86
86
  log_info "Uninstalling Hermes Agent..."
87
87
  mkdir -p "$(dirname "$LOG_FILE")"
88
88
 
89
- loading "Removing Hermes Agent" _uninstall_hermes_agent_impl
89
+ loading "Removing Hermes Agent" _uninstall_hermes_agent_impl || return 1
90
90
 
91
91
  log_success "Hermes Agent uninstalled successfully"
92
92
  return 0
@@ -0,0 +1,34 @@
1
+ # Hugging Face CLI
2
+
3
+ Official `hf` command-line client for the Hugging Face Hub, installed in an
4
+ isolated Python environment so it does not modify the user's global packages.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ karnel install ai --hugging-face
10
+ hf auth login
11
+ hf auth whoami
12
+ ```
13
+
14
+ Common commands:
15
+
16
+ ```bash
17
+ hf models ls --search qwen --limit 10
18
+ hf download Cactus-Compute/needle
19
+ hf cache ls
20
+ ```
21
+
22
+ Karnel stores the managed environment under
23
+ `~/.local/share/karnel-data/hugging-face`. Uninstalling removes only that
24
+ environment and the Karnel-owned `hf` command. Credentials and downloaded
25
+ models under `~/.cache/huggingface` are preserved.
26
+
27
+ The optional Xet accelerator is disabled because it does not publish an
28
+ Android wheel. Downloads use the standard Hugging Face HTTP transport instead.
29
+
30
+ Do not put Hugging Face tokens in shell history, repositories, screenshots, or
31
+ issue reports. Prefer the interactive `hf auth login` flow or the `HF_TOKEN`
32
+ environment variable.
33
+
34
+ Official documentation: <https://huggingface.co/docs/huggingface_hub/guides/cli>
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env bash
2
+
3
+ import "@/utils/log"
4
+
5
+ : "${KARNEL_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
6
+ HUGGING_FACE_DATA_DIR="$KARNEL_DATA/hugging-face"
7
+ HUGGING_FACE_VENV="$HUGGING_FACE_DATA_DIR/venv"
8
+ HUGGING_FACE_WRAPPER="$PREFIX/bin/hf"
9
+
10
+ _hugging_face_data_owned() {
11
+ [[ -f "$HUGGING_FACE_DATA_DIR/.karnel-managed" ]]
12
+ }
13
+
14
+ _hugging_face_wrapper_owned() {
15
+ local marker="$HUGGING_FACE_DATA_DIR/.karnel-wrapper-hf"
16
+ [[ -f "$marker" && -f "$HUGGING_FACE_WRAPPER" ]] || return 1
17
+ [[ "$(sha256sum "$HUGGING_FACE_WRAPPER" 2>/dev/null)" == "$(<"$marker")" ]]
18
+ }
19
+
20
+ _hugging_face_verify_ownership() {
21
+ if [[ -e "$HUGGING_FACE_DATA_DIR" ]] && ! _hugging_face_data_owned; then
22
+ log_error "Refusing to replace unowned Hugging Face data: $HUGGING_FACE_DATA_DIR"
23
+ return 1
24
+ fi
25
+ if [[ -e "$HUGGING_FACE_WRAPPER" || -L "$HUGGING_FACE_WRAPPER" ]] && ! _hugging_face_wrapper_owned; then
26
+ log_error "Refusing to replace unowned command: $HUGGING_FACE_WRAPPER"
27
+ return 1
28
+ fi
29
+ if command -v hf &>/dev/null && ! _hugging_face_wrapper_owned; then
30
+ log_error "Refusing to shadow the existing hf command: $(command -v hf)"
31
+ return 1
32
+ fi
33
+ }
34
+
35
+ _hugging_face_validate() {
36
+ "$HUGGING_FACE_VENV/bin/hf" --help &>/dev/null
37
+ }
38
+
39
+ _hugging_face_install_packages() {
40
+ local python="$HUGGING_FACE_VENV/bin/python"
41
+ "$python" -m pip install --upgrade \
42
+ 'click>=8.1' 'filelock>=3.10' 'fsspec>=2023.5' 'httpx>=0.23,<1' \
43
+ 'packaging>=20.9' 'pyyaml>=5.1' shellingham 'tqdm>=4.42.1' \
44
+ typer-slim 'typing-extensions>=3.7.4.3' || return 1
45
+ # hf-xet has no Android wheel and its Rust bootstrap rejects the Android target.
46
+ "$python" -m pip install --upgrade --no-deps 'huggingface_hub>=1,<2'
47
+ }
48
+
49
+ _hugging_face_write_wrapper() {
50
+ local temporary
51
+ temporary="$(mktemp "$PREFIX/bin/.hf.XXXXXX")" || return 1
52
+ cat >"$temporary" <<EOF
53
+ #!$PREFIX/bin/bash
54
+ # Karnel-managed Hugging Face CLI wrapper
55
+ export HF_HUB_DISABLE_XET=1
56
+ exec $HUGGING_FACE_VENV/bin/hf "\$@"
57
+ EOF
58
+ chmod 755 "$temporary" || { rm -f "$temporary"; return 1; }
59
+ mv -f "$temporary" "$HUGGING_FACE_WRAPPER" || return 1
60
+ sha256sum "$HUGGING_FACE_WRAPPER" >"$HUGGING_FACE_DATA_DIR/.karnel-wrapper-hf" || return 1
61
+ }
62
+
63
+ _install_hugging_face_impl() {
64
+ if ! command -v python3 &>/dev/null; then
65
+ pkg install python -y || return 1
66
+ fi
67
+ python3 -c 'import sys; raise SystemExit(sys.version_info < (3, 9))' || {
68
+ log_error "Hugging Face CLI requires Python 3.9 or newer"
69
+ return 1
70
+ }
71
+
72
+ mkdir -p "$HUGGING_FACE_DATA_DIR" "$PREFIX/bin" || return 1
73
+ : >"$HUGGING_FACE_DATA_DIR/.karnel-managed" || return 1
74
+ rm -rf "$HUGGING_FACE_VENV"
75
+ python3 -m venv "$HUGGING_FACE_VENV" || return 1
76
+ _hugging_face_install_packages || return 1
77
+ _hugging_face_validate || return 1
78
+ _hugging_face_write_wrapper
79
+ }
80
+
81
+ install_hugging_face() {
82
+ if _hugging_face_data_owned && _hugging_face_wrapper_owned && _hugging_face_validate; then
83
+ log_info "Hugging Face CLI is already installed"
84
+ return 2
85
+ fi
86
+ _hugging_face_verify_ownership || return 1
87
+
88
+ log_info "Installing Hugging Face CLI in an isolated environment..."
89
+ if ! _install_hugging_face_impl; then
90
+ _hugging_face_wrapper_owned && rm -f "$HUGGING_FACE_WRAPPER"
91
+ _hugging_face_data_owned && rm -rf "$HUGGING_FACE_DATA_DIR"
92
+ log_error "Failed to install Hugging Face CLI"
93
+ return 1
94
+ fi
95
+ log_success "Hugging Face CLI installed"
96
+ log_info "Authenticate with: hf auth login"
97
+ }
98
+
99
+ uninstall_hugging_face() {
100
+ if ! _hugging_face_data_owned && ! _hugging_face_wrapper_owned; then
101
+ if [[ -e "$HUGGING_FACE_DATA_DIR" || -e "$HUGGING_FACE_WRAPPER" || -L "$HUGGING_FACE_WRAPPER" ]]; then
102
+ _hugging_face_verify_ownership
103
+ return $?
104
+ fi
105
+ log_info "Hugging Face CLI is not installed by Karnel"
106
+ return 2
107
+ fi
108
+ _hugging_face_verify_ownership || return 1
109
+
110
+ _hugging_face_wrapper_owned && rm -f "$HUGGING_FACE_WRAPPER"
111
+ _hugging_face_data_owned && rm -rf "$HUGGING_FACE_DATA_DIR"
112
+ log_success "Hugging Face CLI uninstalled; credentials and model cache were preserved"
113
+ }
114
+
115
+ update_hugging_face() {
116
+ _hugging_face_data_owned && _hugging_face_wrapper_owned || {
117
+ log_error "Hugging Face CLI is not installed by Karnel"
118
+ return 1
119
+ }
120
+ _hugging_face_verify_ownership || return 1
121
+ _hugging_face_install_packages || return 1
122
+ _hugging_face_validate || return 1
123
+ log_success "Hugging Face CLI updated"
124
+ }
125
+
126
+ reinstall_hugging_face() {
127
+ uninstall_hugging_face || [[ $? -eq 2 ]] || return 1
128
+ install_hugging_face
129
+ }
@@ -195,10 +195,10 @@ uninstall_kilocode_cli() {
195
195
 
196
196
  if ! _kilocode_wrapper_owned && ! _kilocode_alias_owned && ! _kilocode_data_owned; then
197
197
  log_warn "Kilo Code CLI is not installed"
198
- return 1
198
+ return 2
199
199
  fi
200
200
 
201
- loading "Uninstalling Kilo Code CLI" _uninstall_kilocode_cli_impl
201
+ loading "Uninstalling Kilo Code CLI" _uninstall_kilocode_cli_impl || return 1
202
202
  }
203
203
 
204
204
  _uninstall_kilocode_cli_impl() {
@@ -213,6 +213,6 @@ update_kilocode_cli() {
213
213
  }
214
214
 
215
215
  reinstall_kilocode_cli() {
216
- uninstall_kilocode_cli
216
+ uninstall_kilocode_cli || [[ $? -eq 2 ]] || return 1
217
217
  install_kilocode_cli
218
218
  }
@@ -69,7 +69,7 @@ uninstall_kimi_code() {
69
69
  log_info "Uninstalling Kimi Code..."
70
70
  mkdir -p "$(dirname "$LOG_FILE")"
71
71
 
72
- loading "Removing Kimi Code" _uninstall_kimi_code_impl
72
+ loading "Removing Kimi Code" _uninstall_kimi_code_impl || return 1
73
73
 
74
74
  log_success "Kimi Code uninstalled successfully"
75
75
  return 0
@@ -69,7 +69,7 @@ uninstall_minimax_cli() {
69
69
  log_info "Uninstalling MiniMax CLI..."
70
70
  mkdir -p "$(dirname "$LOG_FILE")"
71
71
 
72
- loading "Removing MiniMax CLI" _uninstall_minimax_cli_impl
72
+ loading "Removing MiniMax CLI" _uninstall_minimax_cli_impl || return 1
73
73
 
74
74
  log_success "MiniMax CLI uninstalled successfully"
75
75
  return 0
@@ -79,7 +79,7 @@ uninstall_mistral_vibe() {
79
79
  log_info "Uninstalling Mistral Vibe..."
80
80
  mkdir -p "$(dirname "$LOG_FILE")"
81
81
 
82
- loading "Removing Mistral Vibe" _uninstall_mistral_vibe_impl
82
+ loading "Removing Mistral Vibe" _uninstall_mistral_vibe_impl || return 1
83
83
 
84
84
  log_success "Mistral Vibe uninstalled"
85
85
  return 0
@@ -226,10 +226,10 @@ _omp_is_proot() {
226
226
  uninstall_oh_my_pi() {
227
227
  mkdir -p "$(dirname "$LOG_FILE")"
228
228
  if _omp_is_native; then
229
- loading "Uninstalling Oh-My-Pi (native)" _uninstall_omp_native_impl
229
+ loading "Uninstalling Oh-My-Pi (native)" _uninstall_omp_native_impl || return 1
230
230
  log_success "Oh-My-Pi (native) uninstalled"
231
231
  elif _omp_is_proot; then
232
- loading "Uninstalling Oh-My-Pi (proot-distro)" _uninstall_omp_proot_impl
232
+ loading "Uninstalling Oh-My-Pi (proot-distro)" _uninstall_omp_proot_impl || return 1
233
233
  log_success "Oh-My-Pi (proot-distro) uninstalled"
234
234
  else
235
235
  log_info "Oh-My-Pi is not installed"
@@ -41,7 +41,7 @@ uninstall_ollama() {
41
41
  log_info "Uninstalling Ollama..."
42
42
  mkdir -p "$(dirname "$LOG_FILE")"
43
43
 
44
- loading "Removing Ollama" _uninstall_ollama_impl
44
+ loading "Removing Ollama" _uninstall_ollama_impl || return 1
45
45
 
46
46
  log_success "Ollama uninstalled"
47
47
  return 0
@@ -70,7 +70,7 @@ uninstall_openclaude() {
70
70
  log_info "Uninstalling OpenClaude..."
71
71
  mkdir -p "$(dirname "$LOG_FILE")"
72
72
 
73
- loading "Removing OpenClaude" _uninstall_openclaude_impl
73
+ loading "Removing OpenClaude" _uninstall_openclaude_impl || return 1
74
74
 
75
75
  log_success "OpenClaude uninstalled"
76
76
  return 0
@@ -72,7 +72,7 @@ uninstall_openclaw() {
72
72
  log_info "Uninstalling OpenClaw..."
73
73
  mkdir -p "$(dirname "$LOG_FILE")"
74
74
 
75
- loading "Removing OpenClaw" _uninstall_openclaw_impl
75
+ loading "Removing OpenClaw" _uninstall_openclaw_impl || return 1
76
76
 
77
77
  log_success "OpenClaw uninstalled"
78
78
  return 0
@@ -67,7 +67,7 @@ uninstall_pi() {
67
67
  log_info "Uninstalling Pi Coding Agent..."
68
68
  mkdir -p "$(dirname "$LOG_FILE")"
69
69
 
70
- loading "Removing Pi Coding Agent" _uninstall_pi_impl
70
+ loading "Removing Pi Coding Agent" _uninstall_pi_impl || return 1
71
71
 
72
72
  log_success "Pi uninstalled"
73
73
  return 0
@@ -259,10 +259,10 @@ uninstall_qoder() {
259
259
 
260
260
  if [ ! -f "$PREFIX/bin/qodercli" ]; then
261
261
  log_warn "Qoder is not installed"
262
- return 1
262
+ return 2
263
263
  fi
264
264
 
265
- loading "Uninstalling Qoder" _uninstall_qoder_impl
265
+ loading "Uninstalling Qoder" _uninstall_qoder_impl || return 1
266
266
  }
267
267
 
268
268
  _uninstall_qoder_impl() {
@@ -71,7 +71,7 @@ uninstall_qwen_code() {
71
71
  log_info "Uninstalling Qwen Code..."
72
72
  mkdir -p "$(dirname "$LOG_FILE")"
73
73
 
74
- loading "Removing Qwen Code" _uninstall_qwen_code_impl
74
+ loading "Removing Qwen Code" _uninstall_qwen_code_impl || return 1
75
75
 
76
76
  log_success "Qwen Code uninstalled"
77
77
  return 0
@@ -13,7 +13,14 @@ _batch_tool_action() {
13
13
 
14
14
  for tool in "${tools[@]}"; do
15
15
  local func_name="${action}_${tool//-/_}"
16
- if declare -f "$func_name" &>/dev/null; then
16
+ if [[ "$module" == "ai" ]] && declare -f _run_ai_tool_action &>/dev/null; then
17
+ _run_ai_tool_action "$action" "$tool"
18
+ case $? in
19
+ 0) ((success_count++));;
20
+ 2) ((skipped_count++));;
21
+ *) ((failed_count++));;
22
+ esac
23
+ elif declare -f "$func_name" &>/dev/null; then
17
24
  "$func_name"
18
25
  case $? in
19
26
  0) ((success_count++));;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karnel-termux",
3
- "version": "4.13.11",
3
+ "version": "4.14.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"