karnel-termux 4.7.7 → 4.8.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 (49) hide show
  1. package/README.md +39 -4
  2. package/karnel/cli/commands/backup.sh +8 -0
  3. package/karnel/cli/commands/doctor/system.sh +2 -0
  4. package/karnel/cli/commands/doctor.sh +8 -0
  5. package/karnel/cli/commands/install.sh +139 -84
  6. package/karnel/cli/commands/list.sh +32 -0
  7. package/karnel/cli/commands/open.sh +10 -6
  8. package/karnel/cli/commands/reinstall.sh +11 -0
  9. package/karnel/cli/commands/robin.sh +656 -0
  10. package/karnel/cli/commands/show.sh +1 -0
  11. package/karnel/cli/commands/start.sh +5 -0
  12. package/karnel/cli/commands/status.sh +11 -0
  13. package/karnel/cli/commands/uninstall.sh +9 -0
  14. package/karnel/cli/commands/update.sh +9 -0
  15. package/karnel/cli/commands/version.sh +2 -0
  16. package/karnel/cli/karnel.sh +35 -25
  17. package/karnel/modules/dev.sh +19 -46
  18. package/karnel/modules/osint.sh +123 -0
  19. package/karnel/tools/ai/crush/install.sh +1 -1
  20. package/karnel/tools/ai/omni-route/install.sh +1 -1
  21. package/karnel/tools/deploy/railway/install.sh +1 -1
  22. package/karnel/tools/dev/all.sh +45 -2
  23. package/karnel/tools/dev/fzf/install.sh +36 -9
  24. package/karnel/tools/npm/turbopack/install.sh +2 -0
  25. package/karnel/tools/osint/all.sh +65 -0
  26. package/karnel/tools/osint/robin/README.md +99 -0
  27. package/karnel/tools/osint/robin/common.sh +258 -0
  28. package/karnel/tools/osint/robin/install.sh +548 -0
  29. package/karnel/tools/osint/robin/requirements-termux.txt +33 -0
  30. package/karnel/utils/env.sh +16 -2
  31. package/karnel/utils/tools.sh +9 -2
  32. package/package.json +6 -1
  33. package/scripts/check-package.js +45 -0
  34. package/scripts/completion.bash +8 -3
  35. package/scripts/completion.zsh +8 -1
  36. package/karnel/tools/dev/applaunch/__pycache__/applaunch.cpython-314.pyc +0 -0
  37. package/karnel/tools/dev/fconv/__pycache__/fconv.cpython-314.pyc +0 -0
  38. package/karnel/tools/dev/filecheck/__pycache__/filecheck.cpython-314.pyc +0 -0
  39. package/karnel/tools/dev/notes/__pycache__/notes.cpython-314.pyc +0 -0
  40. package/karnel/tools/dev/passman/__pycache__/passman.cpython-314.pyc +0 -0
  41. package/karnel/tools/dev/splash/__pycache__/splash.cpython-314.pyc +0 -0
  42. package/karnel/tools/dev/websites/__pycache__/websites.cpython-314.pyc +0 -0
  43. package/karnel/tools/games/arcade/__pycache__/arcade.cpython-314.pyc +0 -0
  44. package/karnel/tools/games/buzz/__pycache__/buzz.cpython-314.pyc +0 -0
  45. package/karnel/tools/games/ctfgod/__pycache__/ctfgod.cpython-314.pyc +0 -0
  46. package/karnel/tools/games/detective/__pycache__/detective.cpython-314.pyc +0 -0
  47. package/karnel/tools/games/pet-friends/__pycache__/pet-friends.cpython-314.pyc +0 -0
  48. package/karnel/tools/games/tamagotchi/__pycache__/tamagotchi.cpython-314.pyc +0 -0
  49. package/karnel/tools/games/terminal-arcade/__pycache__/terminal-arcade.cpython-314.pyc +0 -0
@@ -0,0 +1,548 @@
1
+ #!/usr/bin/env bash
2
+
3
+ import "@/utils/log"
4
+ import "@/utils/version"
5
+ import "@/tools/osint/robin/common"
6
+
7
+ LOG_FILE="$KARNEL_CACHE/install_osint.log"
8
+ ROBIN_INSTALLER_DIR="$(dirname "${BASH_SOURCE[0]}")"
9
+ ROBIN_TERMUX_REQUIREMENTS="$ROBIN_INSTALLER_DIR/requirements-termux.txt"
10
+ ROBIN_STREAMLIT_VERSION="1.59.2"
11
+
12
+ _robin_package_installed() {
13
+ [[ "$(dpkg-query -W -f='${db:Status-Abbrev}' "$1" 2>/dev/null)" == "ii " ]]
14
+ }
15
+
16
+ _robin_verify_termux() {
17
+ if [[ ! -x "$PREFIX/bin/pkg" ]] || [[ "$PREFIX" != *"com.termux"* ]]; then
18
+ log_error "Robin native installation requires a supported Termux environment"
19
+ return 1
20
+ fi
21
+
22
+ local free_kb
23
+ free_kb=$(df -Pk "$HOME" 2>/dev/null | awk 'NR == 2 { print $4 }')
24
+ if [[ "$free_kb" =~ ^[0-9]+$ ]] && (( free_kb < 400000 )); then
25
+ log_error "Robin requires at least 400 MB of free storage"
26
+ return 1
27
+ fi
28
+
29
+ local architecture
30
+ architecture=$(dpkg --print-architecture 2>/dev/null || printf 'unknown')
31
+ if [[ "$architecture" != "aarch64" ]]; then
32
+ log_warn "Robin is validated on Termux aarch64; detected: $architecture"
33
+ fi
34
+ }
35
+
36
+ _robin_verify_python() {
37
+ if ! command -v python3 &>/dev/null; then
38
+ log_info "Installing Python..."
39
+ pkg install python -y &>>"$LOG_FILE" || {
40
+ log_error "Failed to install Python"
41
+ return 1
42
+ }
43
+ fi
44
+
45
+ local version major minor
46
+ version=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null) || {
47
+ log_error "Could not determine the Python version"
48
+ return 1
49
+ }
50
+ major=${version%%.*}
51
+ minor=${version#*.}
52
+ if (( major < 3 || (major == 3 && minor < 10) )); then
53
+ log_error "Python 3.10+ is required. Found: $version"
54
+ return 1
55
+ fi
56
+
57
+ python3 -c 'import venv' &>/dev/null || {
58
+ log_error "The Python venv module is unavailable. Reinstall the Termux python package."
59
+ return 1
60
+ }
61
+ }
62
+
63
+ _robin_install_system_deps() {
64
+ if ! _robin_package_installed tur-repo; then
65
+ log_info "Enabling the Termux User Repository for native Python packages..."
66
+ pkg install tur-repo -y &>>"$LOG_FILE" || {
67
+ log_error "Failed to enable tur-repo"
68
+ return 1
69
+ }
70
+ fi
71
+
72
+ local -a required_packages=(
73
+ git
74
+ tor
75
+ curl
76
+ build-essential
77
+ python-numpy
78
+ python-pandas
79
+ python-pillow
80
+ python-pyarrow
81
+ )
82
+ local -a missing_packages=()
83
+ local package
84
+ for package in "${required_packages[@]}"; do
85
+ _robin_package_installed "$package" || missing_packages+=("$package")
86
+ done
87
+
88
+ if (( ${#missing_packages[@]} > 0 )); then
89
+ log_info "Installing native dependencies: ${missing_packages[*]}"
90
+ pkg install "${missing_packages[@]}" -y &>>"$LOG_FILE" || {
91
+ log_error "Failed to install Robin native dependencies"
92
+ return 1
93
+ }
94
+ fi
95
+ }
96
+
97
+ _robin_migrate_path() {
98
+ local source="$1"
99
+ local destination="$2"
100
+ local label="$3"
101
+ [[ -e "$source" || -L "$source" ]] || return 0
102
+
103
+ if [[ ! -e "$destination" && ! -L "$destination" ]]; then
104
+ mv "$source" "$destination" || return 1
105
+ return 0
106
+ fi
107
+
108
+ local migrated
109
+ migrated="${destination}.migrated.$(date +%Y%m%d_%H%M%S)"
110
+ mv "$source" "$migrated" || return 1
111
+ log_warn "$label already existed; legacy data was preserved at $migrated"
112
+ }
113
+
114
+ _robin_migrate_legacy_layout() {
115
+ _robin_refresh_layout
116
+ [[ "$ROBIN_LAYOUT" == "legacy" ]] || return 0
117
+
118
+ if _robin_runtime_active; then
119
+ log_error "Stop Robin before migrating its installation: karnel robin stop"
120
+ return 1
121
+ fi
122
+
123
+ log_info "Migrating Robin to the managed data layout..."
124
+ mkdir -p "$ROBIN_CONFIG_DIR" "$ROBIN_DATA_DIR"
125
+ chmod 700 "$ROBIN_CONFIG_DIR" "$ROBIN_DATA_DIR" 2>/dev/null || true
126
+
127
+ _robin_migrate_path "$ROBIN_ROOT/.env" "$ROBIN_ENV_FILE" "Robin configuration" || return 1
128
+ _robin_migrate_path "$ROBIN_ROOT/investigations" "$ROBIN_INVESTIGATIONS_DIR" "Robin investigations" || return 1
129
+
130
+ local legacy_root="${ROBIN_ROOT}.legacy.$$"
131
+ mv "$ROBIN_ROOT" "$legacy_root" || return 1
132
+ mkdir -p "$ROBIN_ROOT" || {
133
+ mv "$legacy_root" "$ROBIN_ROOT"
134
+ return 1
135
+ }
136
+
137
+ if [[ -d "$legacy_root/.venv" ]]; then
138
+ mv "$legacy_root/.venv" "$ROBIN_MANAGED_VENV_DIR" || {
139
+ rm -rf "$ROBIN_ROOT"
140
+ mv "$legacy_root" "$ROBIN_ROOT"
141
+ _robin_refresh_layout
142
+ _robin_prepare_persistent_data "$ROBIN_APP_DIR" || true
143
+ return 1
144
+ }
145
+ fi
146
+
147
+ mv "$legacy_root" "$ROBIN_MANAGED_APP_DIR" || {
148
+ [[ -d "$ROBIN_MANAGED_VENV_DIR" ]] && mv "$ROBIN_MANAGED_VENV_DIR" "$legacy_root/.venv"
149
+ rmdir "$ROBIN_ROOT" 2>/dev/null || true
150
+ mv "$legacy_root" "$ROBIN_ROOT" 2>/dev/null || true
151
+ _robin_refresh_layout
152
+ _robin_prepare_persistent_data "$ROBIN_APP_DIR" || true
153
+ log_error "Migration failed; the legacy installation was restored"
154
+ return 1
155
+ }
156
+ (umask 077; printf '%s\n' "managed-by-karnel" > "$ROBIN_ROOT/.managed-by-karnel")
157
+ chmod 700 "$ROBIN_ROOT"
158
+ _robin_refresh_layout
159
+ _robin_prepare_persistent_data "$ROBIN_APP_DIR" || return 1
160
+ log_success "Robin configuration and investigations migrated without data loss"
161
+ }
162
+
163
+ _robin_prepare_persistent_data() {
164
+ local app_dir="$1"
165
+ mkdir -p "$ROBIN_CONFIG_DIR" "$ROBIN_DATA_DIR" "$ROBIN_INVESTIGATIONS_DIR"
166
+ chmod 700 "$ROBIN_CONFIG_DIR" "$ROBIN_DATA_DIR" "$ROBIN_INVESTIGATIONS_DIR" 2>/dev/null || true
167
+
168
+ if [[ ! -f "$ROBIN_ENV_FILE" ]]; then
169
+ if [[ -f "$app_dir/.env.example" ]]; then
170
+ cp "$app_dir/.env.example" "$ROBIN_ENV_FILE" || return 1
171
+ else
172
+ (umask 077; : > "$ROBIN_ENV_FILE") || return 1
173
+ fi
174
+ fi
175
+ chmod 600 "$ROBIN_ENV_FILE" || return 1
176
+
177
+ if [[ -e "$app_dir/.env" && ! -L "$app_dir/.env" ]]; then
178
+ _robin_migrate_path "$app_dir/.env" "$ROBIN_ENV_FILE" "Robin configuration" || return 1
179
+ fi
180
+ rm -f "$app_dir/.env"
181
+ ln -s "$ROBIN_ENV_FILE" "$app_dir/.env" || return 1
182
+
183
+ if [[ -e "$app_dir/investigations" && ! -L "$app_dir/investigations" ]]; then
184
+ _robin_migrate_path "$app_dir/investigations" "$ROBIN_INVESTIGATIONS_DIR" "Robin investigations" || return 1
185
+ fi
186
+ rm -f "$app_dir/investigations"
187
+ ln -s "$ROBIN_INVESTIGATIONS_DIR" "$app_dir/investigations" || return 1
188
+
189
+ if [[ -d "$app_dir/.git/info" ]]; then
190
+ local exclude_file="$app_dir/.git/info/exclude"
191
+ grep -qxF '.env' "$exclude_file" 2>/dev/null || printf '.env\n' >> "$exclude_file"
192
+ grep -qxF 'investigations' "$exclude_file" 2>/dev/null || printf 'investigations\n' >> "$exclude_file"
193
+ fi
194
+ }
195
+
196
+ _robin_clone_candidate() {
197
+ local app_candidate="$1"
198
+ log_info "Downloading Robin $ROBIN_VERSION..."
199
+ git clone --branch "$ROBIN_VERSION" --depth 1 "$ROBIN_REPO" "$app_candidate" &>>"$LOG_FILE" || {
200
+ log_error "Failed to clone Robin $ROBIN_VERSION"
201
+ return 1
202
+ }
203
+
204
+ local actual_commit
205
+ actual_commit=$(git -C "$app_candidate" rev-parse HEAD 2>/dev/null) || return 1
206
+ if [[ "$actual_commit" != "$ROBIN_COMMIT" ]]; then
207
+ log_error "Robin source verification failed"
208
+ log_error "Expected $ROBIN_COMMIT but received $actual_commit"
209
+ return 1
210
+ fi
211
+ }
212
+
213
+ _robin_create_venv() {
214
+ local venv_candidate="$1"
215
+ log_info "Creating the Robin Python environment..."
216
+ python3 -m venv --system-site-packages "$venv_candidate" &>>"$LOG_FILE" || {
217
+ log_error "Failed to create the Robin virtual environment"
218
+ return 1
219
+ }
220
+ }
221
+
222
+ _robin_install_python_deps() {
223
+ local venv_dir="$1"
224
+ local python="$venv_dir/bin/python"
225
+ [[ -x "$python" ]] || return 1
226
+ [[ -f "$ROBIN_TERMUX_REQUIREMENTS" ]] || {
227
+ log_error "Missing Termux requirements: $ROBIN_TERMUX_REQUIREMENTS"
228
+ return 1
229
+ }
230
+
231
+ log_info "Installing Robin Python dependencies..."
232
+ "$python" -m pip install --upgrade pip setuptools wheel &>>"$LOG_FILE" || return 1
233
+ "$python" -m pip install -r "$ROBIN_TERMUX_REQUIREMENTS" &>>"$LOG_FILE" || return 1
234
+
235
+ # Termux supplies pyarrow 25 natively. Streamlit's current metadata still
236
+ # caps pyarrow below 25, although Robin does not use Arrow-specific APIs.
237
+ "$python" -m pip install --no-deps "streamlit==$ROBIN_STREAMLIT_VERSION" &>>"$LOG_FILE" || return 1
238
+ }
239
+
240
+ _robin_verify_install() {
241
+ local app_dir="${1:-$ROBIN_APP_DIR}"
242
+ local venv_dir="${2:-$ROBIN_VENV_DIR}"
243
+ local python="$venv_dir/bin/python"
244
+ [[ -x "$python" && -f "$app_dir/ui.py" ]] || return 1
245
+ "$venv_dir/bin/pip" --version &>>"$LOG_FILE" || return 1
246
+ "$venv_dir/bin/streamlit" --version &>>"$LOG_FILE" || return 1
247
+
248
+ PYTHONPATH="$app_dir" "$python" -c '
249
+ import altair
250
+ import bs4
251
+ import dotenv
252
+ import itsdangerous
253
+ import langchain_anthropic
254
+ import langchain_community
255
+ import langchain_google_genai
256
+ import langchain_ollama
257
+ import langchain_openai
258
+ import numpy
259
+ import pandas
260
+ import PIL
261
+ import pyarrow
262
+ import pydeck
263
+ import requests
264
+ import socks
265
+ import streamlit
266
+ import health
267
+ import llm
268
+ import llm_utils
269
+ import scrape
270
+ import search
271
+ ' &>>"$LOG_FILE" || return 1
272
+
273
+ local source
274
+ for source in "$app_dir"/*.py; do
275
+ "$python" -m py_compile "$source" &>>"$LOG_FILE" || return 1
276
+ done
277
+ }
278
+
279
+ _robin_relocate_venv() {
280
+ local old_path="$1"
281
+ local new_path="$2"
282
+ "$new_path/bin/python" - "$old_path" "$new_path" <<'PY'
283
+ from pathlib import Path
284
+ import sys
285
+
286
+ old = sys.argv[1].encode()
287
+ new = sys.argv[2].encode()
288
+ for path in (Path(sys.argv[2]) / "bin").iterdir():
289
+ if path.is_symlink() or not path.is_file():
290
+ continue
291
+ try:
292
+ content = path.read_bytes()
293
+ except OSError:
294
+ continue
295
+ if old in content:
296
+ path.write_bytes(content.replace(old, new))
297
+ PY
298
+ }
299
+
300
+ _robin_candidate_health() {
301
+ local app_dir="$1"
302
+ local venv_dir="$2"
303
+ local python="$venv_dir/bin/python"
304
+ local port
305
+ port=$(
306
+ "$python" -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()'
307
+ ) || return 1
308
+
309
+ local test_log="$ROBIN_ROOT/.health-check.log"
310
+ (
311
+ cd "$app_dir" || exit 1
312
+ "$python" -m streamlit run ui.py \
313
+ --server.address 127.0.0.1 \
314
+ --server.port "$port" \
315
+ --server.headless true \
316
+ --server.fileWatcherType none \
317
+ --browser.gatherUsageStats false
318
+ ) &>"$test_log" &
319
+ local pid=$!
320
+
321
+ local healthy=false
322
+ local attempt
323
+ for ((attempt = 0; attempt < 45; attempt++)); do
324
+ if ! kill -0 "$pid" 2>/dev/null; then
325
+ break
326
+ fi
327
+ if curl --fail --silent --max-time 2 "http://127.0.0.1:$port/_stcore/health" 2>/dev/null | grep -qx "ok"; then
328
+ healthy=true
329
+ break
330
+ fi
331
+ sleep 1
332
+ done
333
+
334
+ kill "$pid" 2>/dev/null || true
335
+ for ((attempt = 0; attempt < 10; attempt++)); do
336
+ kill -0 "$pid" 2>/dev/null || break
337
+ sleep 1
338
+ done
339
+ if kill -0 "$pid" 2>/dev/null; then
340
+ kill -9 "$pid" 2>/dev/null || true
341
+ fi
342
+ if kill -0 "$pid" 2>/dev/null; then
343
+ log_error "Could not terminate the temporary Streamlit health process"
344
+ return 1
345
+ fi
346
+ wait "$pid" 2>/dev/null || true
347
+ if $healthy; then
348
+ rm -f "$test_log"
349
+ return 0
350
+ fi
351
+
352
+ log_error "Streamlit health check failed. Details: $test_log"
353
+ return 1
354
+ }
355
+
356
+ _robin_swap_candidate() {
357
+ local app_candidate="$1"
358
+ local venv_candidate="$2"
359
+ local app_backup="$ROBIN_ROOT/.app-backup.$$"
360
+ local venv_backup="$ROBIN_ROOT/.venv-backup.$$"
361
+
362
+ local had_app=false
363
+ local had_venv=false
364
+ if [[ -d "$ROBIN_MANAGED_APP_DIR" ]]; then
365
+ mv "$ROBIN_MANAGED_APP_DIR" "$app_backup" || {
366
+ log_error "Could not back up the active Robin source"
367
+ return 1
368
+ }
369
+ had_app=true
370
+ fi
371
+ if [[ -d "$ROBIN_MANAGED_VENV_DIR" ]]; then
372
+ if ! mv "$ROBIN_MANAGED_VENV_DIR" "$venv_backup"; then
373
+ $had_app && mv "$app_backup" "$ROBIN_MANAGED_APP_DIR"
374
+ log_error "Could not back up the active Robin environment"
375
+ return 1
376
+ fi
377
+ had_venv=true
378
+ fi
379
+
380
+ if ! mv "$app_candidate" "$ROBIN_MANAGED_APP_DIR"; then
381
+ $had_app && mv "$app_backup" "$ROBIN_MANAGED_APP_DIR"
382
+ $had_venv && mv "$venv_backup" "$ROBIN_MANAGED_VENV_DIR"
383
+ log_error "Failed to activate the new Robin source; previous version restored"
384
+ return 1
385
+ fi
386
+ if ! mv "$venv_candidate" "$ROBIN_MANAGED_VENV_DIR"; then
387
+ rm -rf "$ROBIN_MANAGED_APP_DIR"
388
+ $had_app && mv "$app_backup" "$ROBIN_MANAGED_APP_DIR"
389
+ $had_venv && mv "$venv_backup" "$ROBIN_MANAGED_VENV_DIR"
390
+ log_error "Failed to activate the new Robin installation; previous version restored"
391
+ return 1
392
+ fi
393
+
394
+ if ! _robin_relocate_venv "$venv_candidate" "$ROBIN_MANAGED_VENV_DIR"; then
395
+ rm -rf "$ROBIN_MANAGED_APP_DIR" "$ROBIN_MANAGED_VENV_DIR"
396
+ [[ -d "$app_backup" ]] && mv "$app_backup" "$ROBIN_MANAGED_APP_DIR"
397
+ [[ -d "$venv_backup" ]] && mv "$venv_backup" "$ROBIN_MANAGED_VENV_DIR"
398
+ log_error "Failed to relocate the Robin environment; previous version restored"
399
+ return 1
400
+ fi
401
+
402
+ _robin_refresh_layout
403
+ if ! _robin_verify_install; then
404
+ rm -rf "$ROBIN_MANAGED_APP_DIR" "$ROBIN_MANAGED_VENV_DIR"
405
+ [[ -d "$app_backup" ]] && mv "$app_backup" "$ROBIN_MANAGED_APP_DIR"
406
+ [[ -d "$venv_backup" ]] && mv "$venv_backup" "$ROBIN_MANAGED_VENV_DIR"
407
+ _robin_refresh_layout
408
+ log_error "Final Robin verification failed; previous version restored"
409
+ return 1
410
+ fi
411
+
412
+ rm -rf "$app_backup" "$venv_backup"
413
+ (umask 077; printf 'version=%s\ncommit=%s\n' "$ROBIN_VERSION" "$ROBIN_COMMIT" > "$ROBIN_ROOT/.managed-by-karnel")
414
+ }
415
+
416
+ _robin_install_release() {
417
+ _robin_verify_termux || return 1
418
+ _robin_verify_python || return 1
419
+ _robin_install_system_deps || return 1
420
+ _robin_migrate_legacy_layout || return 1
421
+
422
+ if _robin_runtime_active; then
423
+ log_error "Stop Robin before changing its installation: karnel robin stop"
424
+ return 1
425
+ fi
426
+
427
+ mkdir -p "$ROBIN_ROOT" "$KARNEL_CACHE"
428
+ chmod 700 "$ROBIN_ROOT" 2>/dev/null || true
429
+ local app_candidate="$ROBIN_ROOT/.app-candidate.$$"
430
+ local venv_candidate="$ROBIN_ROOT/.venv-candidate.$$"
431
+ rm -rf "$app_candidate" "$venv_candidate"
432
+
433
+ if ! _robin_clone_candidate "$app_candidate" ||
434
+ ! _robin_prepare_persistent_data "$app_candidate" ||
435
+ ! _robin_create_venv "$venv_candidate" ||
436
+ ! _robin_install_python_deps "$venv_candidate" ||
437
+ ! _robin_verify_install "$app_candidate" "$venv_candidate" ||
438
+ ! _robin_candidate_health "$app_candidate" "$venv_candidate"; then
439
+ rm -rf "$app_candidate" "$venv_candidate"
440
+ log_error "Robin installation failed without changing the active version"
441
+ log_info "Installation log: $LOG_FILE"
442
+ return 1
443
+ fi
444
+
445
+ _robin_swap_candidate "$app_candidate" "$venv_candidate"
446
+ }
447
+
448
+ _robin_current_release() {
449
+ _robin_refresh_layout
450
+ [[ "$ROBIN_LAYOUT" == "managed" ]] || return 1
451
+ [[ -d "$ROBIN_APP_DIR/.git" ]] || return 1
452
+ [[ "$(git -C "$ROBIN_APP_DIR" rev-parse HEAD 2>/dev/null)" == "$ROBIN_COMMIT" ]] || return 1
453
+ _robin_verify_install
454
+ }
455
+
456
+ _robin_show_success() {
457
+ echo
458
+ box "Robin instalado com sucesso"
459
+ echo
460
+ log_success "Release validada: $ROBIN_VERSION"
461
+ log_info "Configure um provedor: karnel robin config"
462
+ log_info "Execute o diagnostico: karnel robin doctor"
463
+ log_info "Inicie a interface: karnel robin start"
464
+ log_info "Abra: http://127.0.0.1:$ROBIN_PORT"
465
+ echo
466
+ log_info "Configuracao e investigacoes ficam fora do codigo e sobrevivem a reinstalacoes."
467
+ echo
468
+ }
469
+
470
+ install_robin() {
471
+ mkdir -p "$(dirname "$LOG_FILE")"
472
+ _robin_acquire_lock || return 1
473
+ if _robin_current_release; then
474
+ log_info "Robin $ROBIN_VERSION is already installed and healthy"
475
+ _robin_release_lock
476
+ return 2
477
+ fi
478
+
479
+ local rc=0
480
+ log_info "Installing Robin $ROBIN_VERSION..."
481
+ _robin_install_release || rc=$?
482
+ _robin_release_lock
483
+ if (( rc == 0 )); then
484
+ _robin_show_success
485
+ fi
486
+ return "$rc"
487
+ }
488
+
489
+ uninstall_robin() {
490
+ _robin_refresh_layout
491
+ if [[ ! -d "$ROBIN_APP_DIR" && ! -d "$ROBIN_VENV_DIR" ]]; then
492
+ log_info "Robin is not installed"
493
+ return 2
494
+ fi
495
+
496
+ _robin_acquire_lock || return 1
497
+ local rc=0
498
+ _robin_migrate_legacy_layout || rc=$?
499
+ if (( rc == 0 )) && _robin_runtime_active; then
500
+ log_error "Stop Robin before uninstalling it: karnel robin stop"
501
+ rc=1
502
+ fi
503
+
504
+ if (( rc == 0 )); then
505
+ rm -rf "$ROBIN_MANAGED_APP_DIR" "$ROBIN_MANAGED_VENV_DIR"
506
+ rm -f "$ROBIN_PID_FILE"
507
+ log_success "Robin application removed"
508
+ log_info "Configuration preserved at: $ROBIN_CONFIG_DIR"
509
+ log_info "Investigations preserved at: $ROBIN_INVESTIGATIONS_DIR"
510
+ fi
511
+ _robin_release_lock
512
+ return "$rc"
513
+ }
514
+
515
+ update_robin() {
516
+ _robin_refresh_layout
517
+ if [[ ! -d "$ROBIN_APP_DIR" ]]; then
518
+ log_error "Robin is not installed. Run: karnel install osint --robin"
519
+ return 1
520
+ fi
521
+ _robin_acquire_lock || return 1
522
+ if _robin_current_release; then
523
+ log_info "Robin $ROBIN_VERSION is already up to date and healthy"
524
+ _robin_release_lock
525
+ return 2
526
+ fi
527
+
528
+ local rc=0
529
+ log_info "Reconciling Robin with pinned release $ROBIN_VERSION..."
530
+ _robin_install_release || rc=$?
531
+ _robin_release_lock
532
+ if (( rc == 0 )); then
533
+ log_success "Robin updated to $ROBIN_VERSION"
534
+ fi
535
+ return "$rc"
536
+ }
537
+
538
+ reinstall_robin() {
539
+ _robin_acquire_lock || return 1
540
+ local rc=0
541
+ log_info "Reinstalling Robin $ROBIN_VERSION without deleting user data..."
542
+ _robin_install_release || rc=$?
543
+ _robin_release_lock
544
+ if (( rc == 0 )); then
545
+ _robin_show_success
546
+ fi
547
+ return "$rc"
548
+ }
@@ -0,0 +1,33 @@
1
+ # Pure-Python dependencies tested with Robin v2.8 on Termux.
2
+ # Native numpy, pandas, Pillow and pyarrow come from pkg.
3
+ beautifulsoup4>=4.12,<5
4
+ PySocks>=1.7,<2
5
+ requests>=2.32,<3
6
+ python-dotenv>=1.0,<2
7
+ backports.tarfile>=1.2,<2
8
+ langchain-openai>=0.3,<2
9
+ langchain-ollama>=0.3,<2
10
+ langchain-anthropic>=0.3,<2
11
+ langchain-community>=0.3,<2
12
+ langchain-google-genai>=2,<5
13
+ altair>=4,<7
14
+ blinker>=1.5,<2
15
+ cachetools>=5.5,<8
16
+ click>=7,<9
17
+ gitpython>=3.0.7,<4
18
+ packaging>=20
19
+ protobuf>=3.20,<8
20
+ pydeck>=0.8.0b4,<1
21
+ rich>=11
22
+ tenacity>=8.1,<10
23
+ toml>=0.10.1,<2
24
+ tornado>=6.0,<7
25
+ typing-extensions>=4.10,<5
26
+ watchdog>=2.1.5,<7
27
+ starlette>=0.40
28
+ uvicorn>=0.30
29
+ httptools>=0.6.3
30
+ anyio>=4
31
+ python-multipart>=0.0.10
32
+ websockets>=12
33
+ itsdangerous>=2.1.2
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- KARNEL_VERSION="4.7.6"
3
+ KARNEL_VERSION="4.8.0"
4
4
 
5
5
  # -------------------------
6
6
  # Directorios del usuario
@@ -23,6 +23,9 @@ KARNEL_BIN="$KARNEL_PATH/bin"
23
23
  KARNEL_MODULES="$KARNEL_PATH/modules"
24
24
  KARNEL_UTILS="$KARNEL_PATH/utils"
25
25
  KARNEL_CLI="$KARNEL_PATH/cli"
26
+ KARNEL_TOOLS="$KARNEL_DATA/tools"
27
+ KARNEL_RUN="$KARNEL_CACHE/run"
28
+ KARNEL_LOGS="$KARNEL_CACHE/logs"
26
29
 
27
30
  # -------------------------
28
31
  # Crear directorios
@@ -31,7 +34,18 @@ KARNEL_CLI="$KARNEL_PATH/cli"
31
34
  mkdir -p \
32
35
  "$KARNEL_CONFIG" \
33
36
  "$KARNEL_CACHE" \
34
- "$KARNEL_DATA"
37
+ "$KARNEL_DATA" \
38
+ "$KARNEL_TOOLS" \
39
+ "$KARNEL_RUN" \
40
+ "$KARNEL_LOGS"
41
+
42
+ chmod 700 \
43
+ "$KARNEL_CONFIG" \
44
+ "$KARNEL_CACHE" \
45
+ "$KARNEL_DATA" \
46
+ "$KARNEL_TOOLS" \
47
+ "$KARNEL_RUN" \
48
+ "$KARNEL_LOGS" 2>/dev/null || true
35
49
 
36
50
  # -------------------------
37
51
  # TUI Colors - Ruby & Obsidian
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env bash
2
+
1
3
  _batch_tool_action() {
2
4
  local module="$1"
3
5
  local action="$2"
@@ -5,6 +7,7 @@ _batch_tool_action() {
5
7
  local -a tools=("$@")
6
8
  local success_count=0
7
9
  local failed_count=0
10
+ local skipped_count=0
8
11
 
9
12
  import "@/tools/$module/all"
10
13
 
@@ -14,7 +17,8 @@ _batch_tool_action() {
14
17
  "$func_name"
15
18
  case $? in
16
19
  0) ((success_count++));;
17
- 1) ((failed_count++));;
20
+ 2) ((skipped_count++));;
21
+ *) ((failed_count++));;
18
22
  esac
19
23
  else
20
24
  log_warn "Unknown $module tool: $tool"
@@ -29,6 +33,9 @@ _batch_tool_action() {
29
33
  if [[ $failed_count -gt 0 ]]; then
30
34
  log_warn "$failed_count tool(s) failed to ${action}"
31
35
  fi
36
+ if [[ $skipped_count -gt 0 ]]; then
37
+ log_info "$skipped_count tool(s) already in the requested state"
38
+ fi
32
39
 
33
- return $failed_count
40
+ (( failed_count == 0 ))
34
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karnel-termux",
3
- "version": "4.7.7",
3
+ "version": "4.8.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"
@@ -13,6 +13,11 @@
13
13
  "LICENSE"
14
14
  ],
15
15
  "scripts": {
16
+ "check:syntax": "bash tests/check-syntax.sh",
17
+ "lint:shell": "bash tests/check-shellcheck.sh",
18
+ "test": "bash tests/run.sh",
19
+ "pack:check": "node scripts/check-package.js",
20
+ "ci": "npm run lint:shell && npm test && npm run pack:check",
16
21
  "postinstall": "node scripts/npm-install.js"
17
22
  },
18
23
  "engines": {