karnel-termux 4.17.8 → 4.17.10

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 +7 -7
  2. package/docs/ARCHITECTURE/index.md +6 -0
  3. package/docs/CHANGELOG.md +69 -0
  4. package/docs/_config.yml +1 -1
  5. package/docs/_layouts/base.html +15 -0
  6. package/docs/assets/css/style.scss +45 -0
  7. package/docs/cli/index.md +7 -1
  8. package/docs/doctor/index.md +7 -1
  9. package/docs/index.md +6 -13
  10. package/docs/troubleshooting/index.md +21 -6
  11. package/karnel/RELEASE_COMMIT +1 -1
  12. package/karnel/cli/commands/brain.sh +2 -2
  13. package/karnel/cli/commands/doctor/code_langs.sh +1 -1
  14. package/karnel/cli/commands/open.sh +1 -1
  15. package/karnel/cli/commands/pg.sh +16 -3
  16. package/karnel/cli/commands/show.sh +14 -2
  17. package/karnel/cli/commands/update.sh +15 -5
  18. package/karnel/tools/ai/cline/install.sh +9 -1
  19. package/karnel/tools/ai/cursor-cli/install.sh +7 -0
  20. package/karnel/tools/ai/droid/install.sh +10 -1
  21. package/karnel/tools/ai/goose/install.sh +1 -1
  22. package/karnel/tools/ai/keelcode/install.sh +1 -0
  23. package/karnel/tools/ai/kiro/install.sh +9 -1
  24. package/karnel/tools/deploy/supabase/install.sh +14 -3
  25. package/karnel/tools/plugins/install.sh +17 -3
  26. package/karnel/tools/security/amass/install.sh +1 -1
  27. package/karnel/tools/security/burpsuite/install.sh +1 -1
  28. package/karnel/tools/security/ffuf/install.sh +1 -1
  29. package/karnel/tools/security/gobuster/install.sh +1 -1
  30. package/karnel/tools/security/subfinder/install.sh +1 -1
  31. package/karnel/tools/security/zap/install.sh +1 -1
  32. package/karnel/tools/utils/zork/zork.sh +8 -3
  33. package/karnel/tools/voice/uninstall.sh +12 -5
  34. package/karnel/utils/agent_actions.sh +26 -5
  35. package/karnel/utils/install.sh +1 -3
  36. package/karnel/utils/log.sh +1 -1
  37. package/karnel/utils/tools.sh +1 -4
  38. package/karnel/utils/version.sh +1 -1
  39. 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.17.8-0078D4?style=for-the-badge" alt="Version">
11
+ <img src="https://img.shields.io/badge/version-4.17.10-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">
@@ -22,8 +22,8 @@
22
22
  <a href="https://termux.dev/">
23
23
  <img src="https://img.shields.io/badge/platform-Termux%20%7C%20Android-0078D4?style=for-the-badge" alt="Platform">
24
24
  </a>
25
- <a href="https://karneltermux.vercel.app">
26
- <img src="https://img.shields.io/badge/Site-karneltermux.vercel.app-0078D4?style=for-the-badge" alt="Website">
25
+ <a href="https://israelmarques1024-dotcom.github.io/karnel-termux">
26
+ <img src="https://img.shields.io/badge/Site-karneltermux.github.io-0078D4?style=for-the-badge" alt="Website">
27
27
  </a>
28
28
  </p>
29
29
 
@@ -61,7 +61,7 @@ Core agent contributions by **devcorex**.
61
61
  ### Via checksummed GitHub release (recommended)
62
62
 
63
63
  ```bash
64
- version=4.17.8
64
+ version=4.17.10
65
65
  tmpdir=$(mktemp -d) && trap 'rm -rf "$tmpdir"' EXIT
66
66
  base="https://github.com/israelmarques1024-dotcom/karnel-termux/releases/download/v$version"
67
67
  curl -fsSL "$base/karnel-termux-install.sh" -o "$tmpdir/karnel-termux-install.sh"
@@ -507,7 +507,7 @@ karnel open ai # Open AI module docs
507
507
  karnel open db # Open DB module docs
508
508
  ```
509
509
 
510
- Documentation loads from https://karneltermux.vercel.app.
510
+ Documentation loads from https://israelmarques1024-dotcom.github.io/karnel-termux.
511
511
 
512
512
  ---
513
513
 
@@ -519,7 +519,7 @@ Documentation loads from https://karneltermux.vercel.app.
519
519
  - [Troubleshooting](docs/troubleshooting/index.md)
520
520
  - [Architecture](docs/ARCHITECTURE/index.md)
521
521
  - [Documentation changelog](docs/CHANGELOG.md)
522
- - [Official website](https://karneltermux.vercel.app)
522
+ - [Official website](https://israelmarques1024-dotcom.github.io/karnel-termux)
523
523
 
524
524
  ---
525
525
 
@@ -651,7 +651,7 @@ MIT © Israel Marques
651
651
  ---
652
652
 
653
653
  <p align="center">
654
- <a href="https://karneltermux.vercel.app">
654
+ <a href="https://israelmarques1024-dotcom.github.io/karnel-termux">
655
655
  <img src="https://img.shields.io/badge/Full%20Documentation-0078D4?style=for-the-badge" alt="Documentation">
656
656
  </a>
657
657
  <a href="https://www.youtube.com/@capideb">
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: Architecture
3
+ permalink: /ARCHITECTURE/
4
+ layout: base
5
+ ---
6
+
1
7
  # Karnel Architecture
2
8
 
3
9
  ## Project Structure
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,74 @@
1
+ ---
2
+ title: Documentation Changelog
3
+ permalink: /CHANGELOG/
4
+ layout: base
5
+ ---
6
+
1
7
  # Documentation Changelog
2
8
 
9
+ ## 4.17.10
10
+
11
+ - Round 10 security/correctness hardening (23 distinct fixes):
12
+ - **Termux `/tmp` fallbacks eliminated** across host-side code
13
+ (`update.sh`, `brain.sh`, `version.sh`, `log.sh`, `agent_actions.sh`,
14
+ `doctor/code_langs.sh`, `supabase`, and the `security/*` tools amass,
15
+ subfinder, ffuf, gobuster, burpsuite, zap) — now use `KARNEL_CACHE`.
16
+ - **`karnel show <module>`** now rejects invalid module names
17
+ (`/`, `..`, `~`), closing a path-traversal / information-disclosure bug.
18
+ - **Agent PLAN (read-only) mode** hardened: blocks `sed`/`awk`/`perl`/
19
+ `xargs`/`make`/`env`/`nohup`/`setsid`, `sed`/`perl -i`, editors,
20
+ `git apply`/`worktree`/`read-tree`/`update-index`, and now also catches
21
+ redirections without a preceding space (`echo x>file`).
22
+ - **Plugin supply chain** fail-closed: installs now require a manifest
23
+ checksum (was silently skipped), and registry installs pin to a committed
24
+ SHA when provided.
25
+ - **`karnel pg backup --schedule`** no longer swallows `crontab` failures
26
+ and uses the escaped DB name to avoid duplicate cron jobs.
27
+ - **Reinstall** now proceeds to install when the uninstall step reports
28
+ "already uninstalled" (rc 2) instead of aborting.
29
+ - **Archive extraction hardened**: `cline`, `goose`, `kiro` AI installers
30
+ and `zork` now use the safe extractors; `zork` also drops literal `/tmp`.
31
+ - Site fully overhauled (navigation, dark mode, GitHub Pages URLs, Liquid
32
+ fixes, consistent front matter).
33
+
34
+ ## 4.17.9
35
+
36
+ - Hardened installer and runtime security across the toolkit:
37
+ - `droid` binary install now refuses when no published SHA-256 digest is
38
+ available (fail-closed) instead of silently skipping verification.
39
+ - `keelcode` and `supabase` now extract archives through the hardened
40
+ `safe_extract_tar` (rejects symlink/absolute-path traversal) instead of
41
+ raw `tar`.
42
+ - Restored OpenCode installation: upstream publishes no checksum, so
43
+ `github_download_and_extract` warns instead of refusing (other tools that
44
+ publish checksums remain fail-closed).
45
+ - `cursor-cli` now validates the downloaded bundle is a real gzip archive
46
+ before extraction.
47
+ - Agent PLAN (read-only) mode write-guard hardened: now blocks `sed -i` /
48
+ `perl -i`, full-screen editors, and file descriptors like `2>file`.
49
+ - Agent compaction no longer drops the freshly built summary during the
50
+ trim safety-net.
51
+ - `voice` uninstall no longer removes `termux-api` unless Karnel installed it.
52
+ - `karnel show` no longer depends on `realpath` (portable fallback).
53
+ - `karnel update` reports the accurate installed version instead of a
54
+ misleading `latest`.
55
+ - Reinstall no longer deletes the ownership marker before confirming the
56
+ install, preventing a tool from being orphaned on install failure.
57
+
58
+ ## 4.17.8
59
+
60
+ - Documentation site reconciled with the GitHub Pages deployment: the `Publish
61
+ Docs` workflow (`.github/workflows/docs.yml`) builds Jekyll from `docs/` and
62
+ deploys to GitHub Pages, so the live documentation URL is
63
+ `https://israelmarques1024-dotcom.github.io/karnel-termux/`.
64
+ - `docs/CHANGELOG.md` is now a rendered page (added front matter + permalink)
65
+ and all internal links to it use the clean `/CHANGELOG/` path.
66
+ - Tool counts verified against the actual `karnel/tools/*` and
67
+ `karnel/tools/ai/*` directories: 45 AI tools, 3 editors, 8 languages,
68
+ 5 databases, 22 dev tools, 11 npm packages, 10 shell plugins, 4 UI
69
+ components, 4 deploy CLIs, 6 games, 2 network tools, 12 utility tools,
70
+ 30 security tools, 1 automation tool.
71
+
3
72
  ## 4.17.7
4
73
 
5
74
  - Documentation site overhauled: valid `_config.yml`, cayman-compatible custom
package/docs/_config.yml CHANGED
@@ -7,7 +7,7 @@ lang: en-US
7
7
  theme: jekyll-theme-cayman
8
8
  markdown: kramdown
9
9
  logo: /assets/images/karnel-logo.png
10
- custom_css: /assets/css/style.css
10
+ custom_css: /karnel-termux/assets/css/style.css
11
11
  baseurl: /karnel-termux
12
12
  show_downloads: false
13
13
  exclude:
@@ -0,0 +1,15 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <nav class="site-nav" aria-label="Documentation">
6
+ <a href="{{ '/' | relative_url }}">Docs</a>
7
+ <a href="{{ '/cli/' | relative_url }}">CLI</a>
8
+ <a href="{{ '/doctor/' | relative_url }}">Doctor</a>
9
+ <a href="{{ '/troubleshooting/' | relative_url }}">Troubleshooting</a>
10
+ <a href="{{ '/ARCHITECTURE/' | relative_url }}">Architecture</a>
11
+ <a href="{{ '/CHANGELOG/' | relative_url }}">Changelog</a>
12
+ <a href="https://israelmarques1024-dotcom.github.io/karnel-termux/">Live Site</a>
13
+ </nav>
14
+
15
+ {{ content }}
@@ -14,6 +14,7 @@
14
14
  --karnel-text: #c9d1d9;
15
15
  --karnel-muted: #8b949e;
16
16
  --karnel-code-bg: #161b22;
17
+ color-scheme: dark light;
17
18
  }
18
19
 
19
20
  html {
@@ -37,13 +38,23 @@ body {
37
38
  }
38
39
 
39
40
  @media (prefers-color-scheme: dark) {
41
+ html {
42
+ background: var(--karnel-bg);
43
+ }
40
44
  body {
41
45
  background: var(--karnel-bg);
42
46
  color: var(--karnel-text);
43
47
  }
48
+ .main-content {
49
+ color: var(--karnel-text);
50
+ }
44
51
  .page-header {
45
52
  background-image: linear-gradient(120deg, #0078d4, #2f81f7);
46
53
  }
54
+ .page-header .project-name,
55
+ .page-header .project-tagline {
56
+ color: #ffffff;
57
+ }
47
58
  .main-content h1,
48
59
  .main-content h2,
49
60
  .main-content h3,
@@ -235,3 +246,37 @@ body {
235
246
  border-top: 1px solid #e1e4e8;
236
247
  padding-top: 1rem;
237
248
  }
249
+
250
+ /* Top navigation bar (injected by _layouts/base.html) */
251
+ .site-nav {
252
+ display: flex;
253
+ flex-wrap: wrap;
254
+ gap: 0.4rem 1.1rem;
255
+ align-items: center;
256
+ padding: 0.7rem 1rem;
257
+ margin-bottom: 1.75rem;
258
+ background: #f3f5f8;
259
+ border: 1px solid #d0d7de;
260
+ border-radius: 10px;
261
+ }
262
+
263
+ .site-nav a {
264
+ color: #0078d4;
265
+ text-decoration: none;
266
+ font-weight: 600;
267
+ font-size: 0.92rem;
268
+ }
269
+
270
+ .site-nav a:hover {
271
+ text-decoration: underline;
272
+ }
273
+
274
+ @media (prefers-color-scheme: dark) {
275
+ .site-nav {
276
+ background: var(--karnel-panel);
277
+ border-color: var(--karnel-border);
278
+ }
279
+ .site-nav a {
280
+ color: var(--karnel-accent);
281
+ }
282
+ }
package/docs/cli/index.md CHANGED
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: CLI Commands
3
+ permalink: /cli/
4
+ layout: base
5
+ ---
6
+
1
7
  # Karnel CLI Reference
2
8
 
3
9
  ```bash
@@ -190,7 +196,7 @@ karnel open <target>
190
196
  ```
191
197
 
192
198
  Opens the official documentation page for a module using
193
- `termux-open-url`. Base URL: `https://karneltermux.vercel.app`
199
+ `termux-open-url`. Base URL: `https://israelmarques1024-dotcom.github.io/karnel-termux`
194
200
 
195
201
  | Target | Opens |
196
202
  |------------------|-------|
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: Doctor System
3
+ permalink: /doctor/
4
+ layout: base
5
+ ---
6
+
1
7
  # Karnel Doctor
2
8
 
3
9
  `karnel doctor` diagnoses the Termux environment, analyzes project code, and
@@ -239,5 +245,5 @@ Regression coverage lives in `tests/smoke.sh` and verifies nested module syntax,
239
245
  the 76-entry registry, pipeline-safe parsing, mode counts, hidden GitHub workflow
240
246
  detection, scoped NestJS detection, TypeScript, and Python manifests.
241
247
 
242
- See [the 2026-07-16 changelog](../CHANGELOG.md) for the complete audit and hardening
248
+ See [the 2026-07-16 changelog](../CHANGELOG/) for the complete audit and hardening
243
249
  record.
package/docs/index.md CHANGED
@@ -1,20 +1,13 @@
1
- # Karnel Termux Documentation
1
+ ---
2
+ title: Karnel Termux Documentation
3
+ permalink: /
4
+ layout: base
5
+ ---
2
6
 
3
7
  <img class="karnel-logo" src="{{ '/assets/images/karnel-logo.png' | relative_url }}" alt="Karnel Termux">
4
8
 
5
9
  > Modular Dev Environment for Termux — install languages, databases, AI agents, editors, and more with one command
6
10
 
7
- Navigate the docs:
8
-
9
- <ul class="karnel-nav">
10
- <li><a href="./cli/">CLI Commands</a></li>
11
- <li><a href="./doctor/">Doctor System</a></li>
12
- <li><a href="./troubleshooting/">Troubleshooting</a></li>
13
- <li><a href="./ARCHITECTURE/">Architecture</a></li>
14
- <li><a href="./CHANGELOG.md">Changelog</a></li>
15
- <li><a href="https://israelmarques1024-dotcom.github.io/karnel-termux/">Live Site</a></li>
16
- </ul>
17
-
18
11
  ## What is Karnel?
19
12
 
20
13
  Karnel Termux transforms your Android device into a complete development workstation.
@@ -38,7 +31,7 @@ voice commands, and reviewed plugins — all from modules: `ai`, `auto`, `db`,
38
31
  <a href="https://github.com/israelmarques1024-dotcom/karnel-termux/blob/main/karnel/tools/utils/fconv/README.md">Utility Scripts — fconv, notes, treex, and more</a>
39
32
  <a href="./cli/#supabase--remote-project-helpers">Supabase CLI — remote-project helpers</a>
40
33
  <a href="./ARCHITECTURE/">Architecture — project structure and module system</a>
41
- <a href="./CHANGELOG.md">Changelog — version history and fixes</a>
34
+ <a href="./CHANGELOG/">Changelog — version history and fixes</a>
42
35
  </div>
43
36
 
44
37
  ## Credits
@@ -1,3 +1,9 @@
1
+ ---
2
+ title: Troubleshooting
3
+ permalink: /troubleshooting/
4
+ layout: base
5
+ ---
6
+
1
7
  # Troubleshooting
2
8
 
3
9
  Common issues when installing, updating, or publishing Karnel Termux.
@@ -43,12 +49,21 @@ npm publish does not block the release. The workflow needs `contents: write`
43
49
  permission (already declared). If `gh release create` fails, confirm the tag
44
50
  exists and the token used by GitHub Actions has write access.
45
51
 
46
- ## Vercel deployment
47
-
48
- The live site deploys automatically through the Vercel integration on push to
49
- `main`; no GitHub Actions workflow is required. If a deploy fails, check the
50
- Vercel dashboard for build logs and confirm the framework preset, install
51
- command, and environment variables.
52
+ ## Documentation site (GitHub Pages)
53
+
54
+ The documentation site is a Jekyll (cayman theme) site built from the `docs/`
55
+ directory and published to GitHub Pages by the `Publish Docs` workflow
56
+ (`.github/workflows/docs.yml`) on every push to `main` that changes `docs/**`.
57
+ The workflow runs `actions/jekyll-build-pages` (from `docs/`, output to
58
+ `docs/_site`), uploads the result as a Pages artifact, and deploys it to the
59
+ `github-pages` environment. The live site is
60
+ `https://israelmarques1024-dotcom.github.io/karnel-termux/`.
61
+
62
+ If a build fails, open the Actions run for the failing job and read the Jekyll
63
+ output: the usual causes are invalid `_config.yml` YAML, an SCSS compile error
64
+ in `assets/css/style.scss`, or a malformed `{% raw %}{{ }}{% endraw %}` Liquid tag. After a
65
+ successful deploy, the published site is visible under the repository's
66
+ **Settings → Pages**.
52
67
 
53
68
  ## Reporting a bug
54
69
 
@@ -1 +1 @@
1
- ad787cd9d5efa67b498b0b1dc5e00203553b32e7
1
+ ef2964062663afea85983ccb2043d0a8ab6e115e
@@ -121,7 +121,7 @@ _brain_slug_escape() {
121
121
  _brain_update_related() {
122
122
  local file="$1" slug="$2"
123
123
  local tmp
124
- tmp=$(mktemp "${TMPDIR:-/tmp}/karnel-brain.XXXXXX")
124
+ tmp=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-brain.XXXXXX")
125
125
  local frontmatter_end
126
126
  frontmatter_end=$(awk '/^---$/ {++n} n==2 {print NR; exit}' "$file")
127
127
 
@@ -151,7 +151,7 @@ _brain_update_related() {
151
151
  _brain_remove_related() {
152
152
  local file="$1" slug="$2"
153
153
  local tmp
154
- tmp=$(mktemp "${TMPDIR:-/tmp}/karnel-brain.XXXXXX")
154
+ tmp=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-brain.XXXXXX")
155
155
  local frontmatter_end
156
156
  frontmatter_end=$(awk '/^---$/ {++n} n==2 {print NR; exit}' "$file")
157
157
 
@@ -64,7 +64,7 @@ _init_lang_tools() {
64
64
  LANG_TOOLS["C/C++:clang_tidy"]="clang-tidy|lint|clang-tidy {} -- -std=c17 2>&1 | head -20|||official"
65
65
 
66
66
  # Java
67
- LANG_TOOLS["Java:javac"]="javac|syntax|javac -d \"${TMPDIR:-/tmp}\" -Xlint:all {} 2>&1 | head -10|||official"
67
+ LANG_TOOLS["Java:javac"]="javac|syntax|javac -d \"${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}\" -Xlint:all {} 2>&1 | head -10|||official"
68
68
 
69
69
  # Docker
70
70
  LANG_TOOLS["Docker:hadolint"]="hadolint|lint|hadolint Dockerfile|||community"
@@ -3,7 +3,7 @@
3
3
  import "@/utils/log"
4
4
  import "@/utils/colors"
5
5
 
6
- OPEN_BASE_URL="https://karneltermux.vercel.app"
6
+ OPEN_BASE_URL="https://israelmarques1024-dotcom.github.io/karnel-termux"
7
7
 
8
8
  open_main() {
9
9
  if [[ $# -eq 0 ]]; then
@@ -737,9 +737,22 @@ pg_schedule() {
737
737
  local escaped_db; printf -v escaped_db '%q' "$db_name"
738
738
  local escaped_path; printf -v escaped_path '%q' "$KARNEL_PATH"
739
739
  local job="$cron_expr KARNEL_PATH=$escaped_path $escaped_path/bin/karnel pg backup $escaped_db >/dev/null 2>&1"
740
- (crontab -l 2>/dev/null | grep -v "karnel pg backup $db_name"; echo "$job") | crontab -
741
- log_success "Backup scheduled successfully ($interval) for database '$db_name'!"
742
- list_item "Ensure cron daemon is running (run: 'crond')"
740
+ local tmpcron
741
+ tmpcron="$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-pg.XXXXXX")" || {
742
+ log_error "Failed to schedule backup for database '$db_name'"
743
+ return 1
744
+ }
745
+ {
746
+ crontab -l 2>/dev/null | grep -vF "karnel pg backup $escaped_db"
747
+ echo "$job"
748
+ } >"$tmpcron"
749
+ if crontab "$tmpcron"; then
750
+ log_success "Backup scheduled successfully ($interval) for database '$db_name'!"
751
+ list_item "Ensure cron daemon is running (run: 'crond')"
752
+ else
753
+ log_error "Failed to schedule backup for database '$db_name'"
754
+ fi
755
+ rm -f "$tmpcron"
743
756
  fi
744
757
  echo
745
758
  }
@@ -47,6 +47,11 @@ show_main() {
47
47
  return 1
48
48
  fi
49
49
 
50
+ if [[ ! "$module" =~ ^[A-Za-z0-9_-]+$ ]]; then
51
+ log_error "Invalid module name: $module"
52
+ return 1
53
+ fi
54
+
50
55
  if [[ "$module" == "backup" ]]; then
51
56
  _show_backup_docs "$tool"
52
57
  return
@@ -86,8 +91,15 @@ show_main() {
86
91
  return
87
92
  fi
88
93
 
89
- local readme_path
90
- readme_path="$(realpath "$KARNEL_PATH/tools/$module/$tool/README.md" 2>/dev/null)"
94
+ local readme_path raw
95
+ raw="$KARNEL_PATH/tools/$module/$tool/README.md"
96
+ if command -v realpath &>/dev/null; then
97
+ readme_path="$(realpath "$raw" 2>/dev/null)"
98
+ elif command -v readlink &>/dev/null; then
99
+ readme_path="$(readlink -f "$raw" 2>/dev/null)"
100
+ else
101
+ readme_path="$raw"
102
+ fi
91
103
 
92
104
  if [[ -z "$readme_path" ]]; then
93
105
  log_error "No documentation found for $module/$tool"
@@ -226,9 +226,9 @@ _update_try_curl() {
226
226
  command -v curl &>/dev/null || return 1
227
227
 
228
228
  local meta installer sumfile tag
229
- meta=$(mktemp "${TMPDIR:-/tmp}/karnel-meta.XXXXXX") || return 1
230
- installer=$(mktemp "${TMPDIR:-/tmp}/karnel-install.XXXXXX") || { rm -f "$meta"; return 1; }
231
- sumfile=$(mktemp "${TMPDIR:-/tmp}/karnel-sum.XXXXXX") || { rm -f "$meta" "$installer"; return 1; }
229
+ meta=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-meta.XXXXXX") || return 1
230
+ installer=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-install.XXXXXX") || { rm -f "$meta"; return 1; }
231
+ sumfile=$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-sum.XXXXXX") || { rm -f "$meta" "$installer"; return 1; }
232
232
 
233
233
  log_info "Trying the official curl installer..."
234
234
 
@@ -347,12 +347,22 @@ _update_try_git() {
347
347
  return 1
348
348
  }
349
349
 
350
+ _npm_installed_version() {
351
+ local v pr
352
+ v=$(npm list -g karnel-termux 2>/dev/null | grep -oP 'karnel-termux@\K[0-9.]+')
353
+ if [[ -z "$v" ]]; then
354
+ pr="$(npm root -g 2>/dev/null)/karnel-termux/package.json"
355
+ [[ -f "$pr" ]] && v=$(grep -oP '"version"\s*:\s*"\K[0-9.]+' "$pr")
356
+ fi
357
+ printf '%s' "${v:-unknown}"
358
+ }
359
+
350
360
  _update_try_npm() {
351
361
  command -v npm &>/dev/null || return 1
352
362
  log_info "Trying npm update..."
353
363
  if npm update -g karnel-termux --ignore-scripts 2>/dev/null; then
354
364
  local new_ver
355
- new_ver=$(npm list -g karnel-termux 2>/dev/null | grep karnel-termux | grep -oP '@\K[0-9.]+' || echo "latest")
365
+ new_ver=$(_npm_installed_version)
356
366
  log_success "Updated to v$new_ver via npm"
357
367
  return 0
358
368
  fi
@@ -365,7 +375,7 @@ _update_try_npm_install() {
365
375
  log_info "Trying npm install..."
366
376
  if npm install -g karnel-termux@latest --ignore-scripts 2>/dev/null; then
367
377
  local new_ver
368
- new_ver=$(npm list -g karnel-termux 2>/dev/null | grep karnel-termux | grep -oP '@\K[0-9.]+' || echo "latest")
378
+ new_ver=$(_npm_installed_version)
369
379
  log_success "Updated to v$new_ver via npm install"
370
380
  return 0
371
381
  fi
@@ -3,6 +3,14 @@
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
5
  import "@/utils/colors"
6
+ import "@/utils/install"
7
+
8
+ if ! declare -f safe_extract_tar >/dev/null 2>&1; then
9
+ safe_extract_tar() {
10
+ local archive="$1" outdir="$2" strip_components="${3:-0}"
11
+ tar -xzf "$archive" -C "$outdir" --strip-components="$strip_components"
12
+ }
13
+ fi
6
14
 
7
15
  : "${KARNEL_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
8
16
  : "${KARNEL_CACHE:=${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
@@ -99,7 +107,7 @@ _cline_install_global_impl() {
99
107
 
100
108
  mkdir -p "$CLINE_DATA_DIR"
101
109
  : >"$CLINE_DATA_DIR/$CLINE_MARKER"
102
- if ! tar -xzf "$tarball" -C "$CLINE_DATA_DIR" &>>"$LOG_FILE"; then
110
+ if ! safe_extract_tar "$tarball" "$CLINE_DATA_DIR" &>>"$LOG_FILE"; then
103
111
  rm -f "$tarball"
104
112
  log_error "Failed to extract cline linux-arm64 binary"
105
113
  return 1
@@ -129,11 +129,18 @@ _download_cursor_binary_impl() {
129
129
 
130
130
  # Cursor does not publish a verifiable checksum for this asset, so integrity
131
131
  # relies on HTTPS transport only; upstream does not publish a verifiable checksum.
132
+ # Validate the payload is a real gzip archive before extracting so a corrupted
133
+ # or error response (e.g. an HTML error page) is rejected.
132
134
  if ! curl -fsSL "$download_url" -o "$staging_dir/$tarball" &>>"$LOG_FILE"; then
133
135
  rm -rf "$staging_dir"
134
136
  log_error "Failed to download Cursor CLI binary"
135
137
  return 1
136
138
  fi
139
+ if ! gzip -t "$staging_dir/$tarball" 2>/dev/null; then
140
+ rm -rf "$staging_dir"
141
+ log_error "Downloaded Cursor bundle is not a valid gzip archive"
142
+ return 1
143
+ fi
137
144
 
138
145
  if ! tar -zxf "$staging_dir/$tarball" --strip-components=1 -C "$staging_dir" &>>"$LOG_FILE"; then
139
146
  rm -rf "$staging_dir"
@@ -125,13 +125,22 @@ _download_droid_binary_impl() {
125
125
 
126
126
  local expected actual
127
127
  expected="$(curl -fsSL "$sha_url" 2>/dev/null | awk '{print $1}')"
128
- if [ -n "$expected" ] && command -v sha256sum &>/dev/null; then
128
+ if [[ ! "$expected" =~ ^[0-9a-f]{64}$ ]]; then
129
+ log_error "No published SHA-256 for Droid binary; refusing install"
130
+ rm -f "$DROID_DATA_DIR/droid"
131
+ return 1
132
+ fi
133
+ if command -v sha256sum &>/dev/null; then
129
134
  actual="$(sha256sum "$DROID_DATA_DIR/droid" | awk '{print $1}')"
130
135
  if [ "$actual" != "$expected" ]; then
131
136
  log_error "Checksum verification failed"
132
137
  rm -f "$DROID_DATA_DIR/droid"
133
138
  return 1
134
139
  fi
140
+ else
141
+ log_error "sha256sum unavailable; refusing install"
142
+ rm -f "$DROID_DATA_DIR/droid"
143
+ return 1
135
144
  fi
136
145
 
137
146
  chmod +x "$DROID_DATA_DIR/droid"
@@ -197,7 +197,7 @@ _goose_download_binary_proot_impl() {
197
197
  return 1
198
198
  fi
199
199
  verify_github_release_asset aaif-goose/goose "v${latest_version}" "$tarball" "$GOOSE_DATA_DIR/$tarball" || return 1
200
- if ! tar -xzf "$GOOSE_DATA_DIR/$tarball" -C "$GOOSE_DATA_DIR" &>>"$LOG_FILE"; then
200
+ if ! extract_tarball "$GOOSE_DATA_DIR/$tarball" "$GOOSE_DATA_DIR" &>>"$LOG_FILE"; then
201
201
  rm -f "$GOOSE_DATA_DIR/$tarball"
202
202
  log_error "Failed to extract Goose CLI"
203
203
  return 1
@@ -2,6 +2,7 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/install"
5
6
 
6
7
  if ! declare -f safe_extract_tar >/dev/null 2>&1; then
7
8
  safe_extract_tar() {
@@ -3,6 +3,14 @@
3
3
  import "@/utils/log"
4
4
  import "@/utils/colors"
5
5
  import "@/utils/version"
6
+ import "@/utils/install"
7
+
8
+ if ! declare -f safe_extract_tar >/dev/null 2>&1; then
9
+ safe_extract_tar() {
10
+ local archive="$1" outdir="$2" strip_components="${3:-0}"
11
+ tar -xzf "$archive" -C "$outdir" --strip-components="$strip_components"
12
+ }
13
+ fi
6
14
 
7
15
  : "${KARNEL_CACHE:=${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
8
16
  : "${KARNEL_DATA:=${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
@@ -136,7 +144,7 @@ print(version, download, checksum, sep="\t")
136
144
  fi
137
145
  log_success "Checksum verified"
138
146
 
139
- if tar xzf "$archive" -C "$tmpdir" 2>>"$LOG_FILE"; then
147
+ if safe_extract_tar "$archive" "$tmpdir" 2>>"$LOG_FILE"; then
140
148
  bin_path=$(find "$tmpdir" -name "kiro-cli" -type f -executable 2>/dev/null | head -1)
141
149
  if [[ -z "$bin_path" ]]; then
142
150
  bin_path=$(find "$tmpdir" -name "kiro*" -type f -executable 2>/dev/null | head -1)
@@ -2,6 +2,14 @@
2
2
 
3
3
  import "@/utils/log"
4
4
  import "@/utils/version"
5
+ import "@/utils/install"
6
+
7
+ if ! declare -f safe_extract_tar >/dev/null 2>&1; then
8
+ safe_extract_tar() {
9
+ local archive="$1" outdir="$2" strip_components="${3:-0}"
10
+ tar -xzf "$archive" -C "$outdir" --strip-components="$strip_components"
11
+ }
12
+ fi
5
13
 
6
14
  _SUPABASE_VERSION="2.20.8"
7
15
  _SUPABASE_RELEASE_URL="https://github.com/supabase/cli/releases/download/v${_SUPABASE_VERSION}"
@@ -44,7 +52,7 @@ install_supabase() (
44
52
  local checksum_url="${_SUPABASE_RELEASE_URL}/supabase_${_SUPABASE_VERSION}_checksums.txt"
45
53
  local tmp_dir staged_bin marker_staging="" old_bin="" old_marker=""
46
54
  local _kdir="${KARNEL_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
47
- mkdir -p "$_kdir" 2>/dev/null || _kdir="${TMPDIR:-/tmp}"
55
+ mkdir -p "$_kdir" 2>/dev/null || _kdir="${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}"
48
56
  tmp_dir=$(mktemp -d "$_kdir/supabase.XXXXXX") || return 1
49
57
  staged_bin=""
50
58
  trap 'rm -rf "$tmp_dir"; [[ -z "$staged_bin" ]] || rm -f "$staged_bin"; [[ -z "$marker_staging" ]] || rm -f "$marker_staging"' EXIT
@@ -81,8 +89,11 @@ install_supabase() (
81
89
  fi
82
90
  log_success "Checksum verified"
83
91
 
84
- # Extract
85
- tar -xzf "$tmp_dir/$filename" -C "$tmp_dir" supabase 2>/dev/null
92
+ # Extract (hardened: rejects symlink/absolute-path traversal)
93
+ safe_extract_tar "$tmp_dir/$filename" "$tmp_dir" || {
94
+ log_error "Extraction failed"
95
+ return 1
96
+ }
86
97
  if [[ ! -f "$tmp_dir/supabase" ]]; then
87
98
  log_error "Extraction failed"
88
99
  return 1
@@ -519,7 +519,10 @@ _plugin_verify_checksum() {
519
519
  local expected actual
520
520
 
521
521
  expected="$(jq -r '.checksum // empty' "$manifest")" || return 1
522
- [[ -n "$expected" ]] || return 0
522
+ if [[ -z "$expected" ]]; then
523
+ log_error "Plugin '$(jq -r '.name' "$manifest")' has no checksum; refusing install"
524
+ return 1
525
+ fi
523
526
 
524
527
  actual="$(_plugin_payload_checksum "$plugin_root")" || return 1
525
528
  if [[ "$actual" != "$expected" ]]; then
@@ -882,11 +885,15 @@ _plugin_clone_repository() {
882
885
  local repo="$1"
883
886
  local ref="$2"
884
887
  local destination="$3"
888
+ local commit="$4"
885
889
  local source_url="https://github.com/$repo.git"
886
890
 
887
891
  _plugin_require_command git || return 1
888
892
 
889
- if [[ -n "$ref" ]]; then
893
+ if [[ -n "$commit" ]]; then
894
+ git clone "$source_url" "$destination" || return 1
895
+ git -C "$destination" checkout "$commit" --detach || return 1
896
+ elif [[ -n "$ref" ]]; then
890
897
  git clone --depth=1 --branch "$ref" "$source_url" "$destination"
891
898
  else
892
899
  git clone --depth=1 "$source_url" "$destination"
@@ -1083,6 +1090,13 @@ _plugin_stage_and_install() {
1083
1090
  local replace_existing="$7"
1084
1091
  local staging checkout checkout_root candidate candidate_root manifest_name commit installed_ref expected_commit registry_name
1085
1092
 
1093
+ expected_commit=""
1094
+ registry_name=""
1095
+ if [[ -n "$registry_entry" ]]; then
1096
+ expected_commit="$(jq -r '.commit // empty' <<<"$registry_entry")" || return 1
1097
+ registry_name="$(jq -r '.name' <<<"$registry_entry")" || return 1
1098
+ fi
1099
+
1086
1100
  _plugin_prepare_plugins_dir || return 1
1087
1101
  _plugin_validate_repo "$repo" || return 1
1088
1102
  [[ -z "$ref" ]] || _plugin_ref_is_valid "$ref" || {
@@ -1100,7 +1114,7 @@ _plugin_stage_and_install() {
1100
1114
  }
1101
1115
  checkout="$staging/repository"
1102
1116
 
1103
- if ! _plugin_clone_repository "$repo" "$ref" "$checkout"; then
1117
+ if ! _plugin_clone_repository "$repo" "$ref" "$checkout" "$expected_commit"; then
1104
1118
  log_error "Failed to clone plugin repository '$repo'."
1105
1119
  _plugin_cleanup_staging "$staging"
1106
1120
  return 1
@@ -47,7 +47,7 @@ install_amass() (
47
47
  esac
48
48
  url="https://github.com/owasp-amass/amass/releases/download/v${_AMASS_VERSION}/$asset"
49
49
 
50
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/amass.XXXXXX") || return 1
50
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/amass.XXXXXX") || return 1
51
51
  staged_bin=""
52
52
  trap 'rm -rf "$tmpdir"; [ -z "$staged_bin" ] || rm -f "$staged_bin"' EXIT
53
53
  archive="$tmpdir/amass.zip"
@@ -28,7 +28,7 @@ install_burpsuite() (
28
28
 
29
29
  local url tmpdir staged_dir wrapper old_dir wrapper_backup
30
30
  url="https://portswigger.net/burp/releases/download?product=community&type=Jar&version=${_BURP_VERSION}"
31
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/burpsuite.XXXXXX") || return 1
31
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/burpsuite.XXXXXX") || return 1
32
32
  trap 'rm -rf "$tmpdir"' EXIT
33
33
  staged_dir="$tmpdir/data"
34
34
  mkdir -p "$staged_dir" "$PREFIX/bin" "$(dirname "$_BURP_DIR")" || return 1
@@ -26,7 +26,7 @@ _download_ffuf() (
26
26
  *) return 1 ;;
27
27
  esac
28
28
  url="https://github.com/ffuf/ffuf/releases/download/v${_FFUF_VERSION}/$asset"
29
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/ffuf.XXXXXX") || return 1
29
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/ffuf.XXXXXX") || return 1
30
30
  staged_bin=""
31
31
  trap 'rm -rf "$tmpdir"; [ -z "$staged_bin" ] || rm -f "$staged_bin"' EXIT
32
32
  archive="$tmpdir/ffuf.tar.gz"
@@ -19,7 +19,7 @@ _install_gobuster_bin() (
19
19
  x86_64) checksum="871be404ce5f80c96b864586b3caa90f894598d1a8222ae316c19e5f70e04cfc" ;;
20
20
  esac
21
21
  local url="https://github.com/OJ/gobuster/releases/download/v${_GOBUSTER_VERSION}/$asset"
22
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/gobuster.XXXXXX") || return 1
22
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/gobuster.XXXXXX") || return 1
23
23
  staged_bin=""
24
24
  trap 'rm -rf "$tmpdir"; [ -z "$staged_bin" ] || rm -f "$staged_bin"' EXIT
25
25
  archive="$tmpdir/gobuster.tar.gz"
@@ -47,7 +47,7 @@ install_subfinder() (
47
47
  esac
48
48
  url="https://github.com/projectdiscovery/subfinder/releases/download/v${_SUBFINDER_VERSION}/$asset"
49
49
 
50
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/subfinder.XXXXXX") || return 1
50
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/subfinder.XXXXXX") || return 1
51
51
  staged_bin=""
52
52
  trap 'rm -rf "$tmpdir"; [ -z "$staged_bin" ] || rm -f "$staged_bin"' EXIT
53
53
  archive="$tmpdir/subfinder.zip"
@@ -34,7 +34,7 @@ install_zap() (
34
34
 
35
35
  local url="https://github.com/zaproxy/zaproxy/releases/download/v${_ZAP_VERSION}/$ZAP_ASSET"
36
36
 
37
- tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/zap.XXXXXX") || {
37
+ tmpdir=$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/zap.XXXXXX") || {
38
38
  log_error "Falha ao criar diretório temporário"
39
39
  return 1
40
40
  }
@@ -3,8 +3,13 @@ ZORK_DIR="$HOME/.local/share/karnel-data/zork"
3
3
  if [ ! -f "$ZORK_DIR/zork1.dat" ]; then
4
4
  mkdir -p "$ZORK_DIR"
5
5
  echo "Baixando Zork I..."
6
- curl -sL "https://www.infocom-if.org/downloads/zork1.zip" -o /tmp/zork1.zip
7
- unzip -o /tmp/zork1.zip -d "$ZORK_DIR" 2>/dev/null
8
- rm -f /tmp/zork1.zip
6
+ tmpdir="$(mktemp -d "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/zork.XXXXXX")" || exit 1
7
+ curl -fsSL "https://www.infocom-if.org/downloads/zork1.zip" -o "$tmpdir/zork1.zip"
8
+ if unzip -l "$tmpdir/zork1.zip" 2>/dev/null | grep -qE '\.\.[/\\]|^/|[A-Za-z]:\\'; then
9
+ echo "Refusing potentially unsafe Zork archive" >&2
10
+ rm -rf "$tmpdir"; exit 1
11
+ fi
12
+ unzip -o "$tmpdir/zork1.zip" -d "$ZORK_DIR" 2>/dev/null
13
+ rm -rf "$tmpdir"
9
14
  fi
10
15
  exec frotz "$ZORK_DIR/zork1.dat" "$@"
@@ -3,13 +3,20 @@
3
3
  import "@/utils/log"
4
4
 
5
5
  _uninstall_voice_tool() {
6
+ local karnel_data="${KARNEL_DATA:-${XDG_DATA_HOME:-$HOME/.local/share}/karnel-data}"
7
+ local marker="$karnel_data/voice/.termux-api-managed"
6
8
  if command -v termux-api &>/dev/null; then
7
- log_info "Removing Termux:API..."
8
- if ! pkg uninstall -y termux-api &>>"$LOG_FILE"; then
9
- log_error "Failed to remove Termux:API (see $LOG_FILE)"
10
- return 1
9
+ if [[ -f "$marker" ]]; then
10
+ log_info "Removing Termux:API (installed by Karnel)..."
11
+ if ! pkg uninstall -y termux-api &>>"$LOG_FILE"; then
12
+ log_error "Failed to remove Termux:API (see $LOG_FILE)"
13
+ return 1
14
+ fi
15
+ rm -f "$marker"
16
+ log_success "Termux:API removed"
17
+ else
18
+ log_info "Termux:API is present but was not installed by Karnel; leaving it in place"
11
19
  fi
12
- log_success "Termux:API removed"
13
20
  else
14
21
  log_info "Termux:API is not installed"
15
22
  fi
@@ -481,16 +481,32 @@ _agent_exec_to_file() {
481
481
  # ------------------------------------------------------------
482
482
  _agent_cmd_is_write() {
483
483
  local cmd="$1"
484
- # file redirections (2>&1 / >= are not redirections to a file)
485
- if printf '%s\n' "$cmd" | grep -qE '(^|[;&|[:space:]])(>|>>)[^=&]'; then
484
+ # file redirections: >file >>file 2>file 1>>file &>file (also without a
485
+ # preceding space, e.g. echo x>file). Excludes fd-to-fd like >&2 and
486
+ # numeric comparisons >= / =>.
487
+ if printf '%s\n' "$cmd" | grep -qE '(^|[;&|[:space:]]|[A-Za-z0-9])(>|>>|&>)([^&=]|$)'; then
486
488
  return 0
487
489
  fi
488
490
  # command substitution / backticks execute arbitrary code → write
489
491
  if printf '%s\n' "$cmd" | grep -qE '\$\(|`'; then
490
492
  return 0
491
493
  fi
494
+ # in-place editors: sed -i / perl -i modify files in place
495
+ if printf '%s\n' "$cmd" | grep -qiE '(^|[;&|[:space:]])(sed|perl)[[:space:]][^|;&]*-[A-Za-z]*i[A-Za-z]*([[:space:]]|$)'; then
496
+ return 0
497
+ fi
498
+ # full-screen / line editors that can modify files
499
+ if printf '%s\n' "$cmd" | grep -qiE '(^|[;&|[:space:]])(vim|nvim|emacs|ed|ex|vi|nano)[[:space:]]'; then
500
+ return 0
501
+ fi
502
+ # interpreters that can execute arbitrary code / spawn processes / modify
503
+ # files even without a trailing redirection or -i flag (sed 'w', perl
504
+ # open(">"), awk system(), env/setsid/nohup running a command, make, xargs)
505
+ if printf '%s\n' "$cmd" | grep -qiE '(^|[;&|[:space:]])(sed|awk|perl|xargs|make|env|nohup|setsid)[[:space:]]'; then
506
+ return 0
507
+ fi
492
508
  # blocklist of commands that write to disk / change state / spawn a shell
493
- 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|mongosh|kill|pkill|killall|service|systemctl|halt|reboot|shutdown|sudo|doas|su|pkexec|chroot|bash|sh|dash|zsh|ksh|eval|source|exec|screen|tmux|ssh|scp|sftp|rsync|telnet|nc|ncat|ftp|lftp|rsh|sshpass|socat|crontab|at |iptables|nft|ufw|docker|podman|kubectl|helm)[[:space:]]' && return 0
509
+ 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|apply|am|worktree|read-tree|update-index|write-tree|checkout-index)|pkg (install|uninstall|upgrade|reinstall|update)|apt( |-)|apt-get|dpkg|yum|dnf|pacman|brew|pip|pip3|uv|conda|mysql|psql|sqlite3|redis-cli|mongosh|kill|pkill|killall|service|systemctl|halt|reboot|shutdown|sudo|doas|su|pkexec|chroot|bash|sh|dash|zsh|ksh|eval|source|exec|screen|tmux|ssh|scp|sftp|rsync|telnet|nc|ncat|ftp|lftp|rsh|sshpass|socat|crontab|at |iptables|nft|ufw|docker|podman|kubectl|helm)[[:space:]]' && return 0
494
510
  return 1
495
511
  }
496
512
 
@@ -890,7 +906,7 @@ agent_exec_loading() {
890
906
  local delay=0.08
891
907
  local tmpfile
892
908
  local _ktmpdir="${KARNEL_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
893
- mkdir -p "$_ktmpdir" 2>/dev/null || _ktmpdir="${TMPDIR:-/tmp}"
909
+ mkdir -p "$_ktmpdir" 2>/dev/null || _ktmpdir="${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}"
894
910
  tmpfile="$(mktemp "$_ktmpdir/karnel.XXXXXX" 2>/dev/null)" || tmpfile="$(mktemp 2>/dev/null)"
895
911
 
896
912
  local stty_saved=""
@@ -1359,12 +1375,17 @@ agent_context_footer() {
1359
1375
  agent_history_trim_tokens() {
1360
1376
  local history="$1" budget="${2:-0}"
1361
1377
  (( budget <= 0 )) && { echo "$history"; return; }
1362
- local toks n
1378
+ local toks n drop_idx is_summary
1363
1379
  while :; do
1364
1380
  toks=$(agent_context_tokens "$history")
1365
1381
  (( toks <= budget )) && break
1366
1382
  n=$(printf '%s' "$history" | jq 'length')
1367
1383
  (( n <= 4 )) && break
1384
+ drop_idx=$(printf '%s' "$history" | jq -c 'if .[0].role == "system" then 1 else 0 end')
1385
+ is_summary=$(printf '%s' "$history" | jq -r --argjson i "$drop_idx" '.[$i].content // "" | contains("<compacted_summary>")')
1386
+ if [[ "$is_summary" == "true" ]]; then
1387
+ break
1388
+ fi
1368
1389
  history=$(printf '%s' "$history" | jq -c 'if .[0].role == "system" then .[0:1] + .[2:] else .[1:] end')
1369
1390
  done
1370
1391
  echo "$history"
@@ -291,9 +291,7 @@ github_download_and_extract() {
291
291
  if [[ "$expected" =~ ^[0-9a-f]{64}$ ]]; then
292
292
  verify_sha256 "$tarball" "$expected" || return 1
293
293
  else
294
- log_error "No verifiable SHA-256 digest published for $repo/$asset; refusing install"
295
- rm -f "$tarball"
296
- return 1
294
+ log_warn "No published SHA-256 digest for $repo/$asset; skipping integrity verification"
297
295
  fi
298
296
  extract_tarball "$tarball" "$outdir" || return 1
299
297
  return 0
@@ -430,7 +430,7 @@ loading() {
430
430
  local delay=0.08
431
431
  local tmpfile
432
432
  local _ktmpdir="${KARNEL_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
433
- mkdir -p "$_ktmpdir" 2>/dev/null || _ktmpdir="${TMPDIR:-/tmp}"
433
+ mkdir -p "$_ktmpdir" 2>/dev/null || _ktmpdir="${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}"
434
434
  tmpfile="$(mktemp "$_ktmpdir/karnel.XXXXXX" 2>/dev/null)" || tmpfile="$(mktemp 2>/dev/null)"
435
435
  local cmd_pid=""
436
436
 
@@ -59,10 +59,7 @@ _run_tool_lifecycle_action() {
59
59
  fi
60
60
  "$uninstall_handler"
61
61
  rc=$?
62
- (( rc == 0 )) || return "$rc"
63
- if (( protected )); then
64
- rm -f "$marker" || return 1
65
- fi
62
+ (( rc == 0 || rc == 2 )) || return "$rc"
66
63
  "$install_handler"
67
64
  rc=$?
68
65
  if (( rc == 0 && protected )); then
@@ -52,7 +52,7 @@ _spin_capture() {
52
52
  local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
53
53
  local tmp
54
54
  local _ktmpdir="${KARNEL_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/karnel}"
55
- mkdir -p "$_ktmpdir" 2>/dev/null || _ktmpdir="${TMPDIR:-/tmp}"
55
+ mkdir -p "$_ktmpdir" 2>/dev/null || _ktmpdir="${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}"
56
56
  tmp=$(mktemp "$_ktmpdir/karnel.XXXXXX" 2>/dev/null) || tmp=$(mktemp 2>/dev/null)
57
57
  local spinner_fd
58
58
  if [ -c /dev/tty ] 2>/dev/null; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karnel-termux",
3
- "version": "4.17.8",
3
+ "version": "4.17.10",
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"