karnel-termux 4.17.21 → 4.17.22
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.
- package/README.md +2 -2
- package/docs/CHANGELOG.md +8 -0
- package/karnel/RELEASE_COMMIT +1 -1
- package/karnel/cli/commands/agent.sh +9 -5
- package/karnel/cli/commands/brain.sh +18 -14
- package/karnel/cli/commands/ia.sh +5 -1
- package/karnel/cli/commands/pg.sh +5 -2
- package/karnel/cli/commands/restore.sh +3 -0
- package/karnel/cli/commands/voice.sh +4 -2
- package/karnel/tools/lang/bun/install.sh +2 -2
- package/karnel/utils/agent_actions.sh +8 -0
- package/karnel/utils/agent_llm.sh +7 -7
- 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.
|
|
11
|
+
<img src="https://img.shields.io/badge/version-4.17.22-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">
|
|
@@ -78,7 +78,7 @@ Why it's great:
|
|
|
78
78
|
### Via checksummed GitHub release (recommended)
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
version=4.17.
|
|
81
|
+
version=4.17.22
|
|
82
82
|
tmpdir=$(mktemp -d) && trap 'rm -rf "$tmpdir"' EXIT
|
|
83
83
|
base="https://github.com/israelmarques1024-dotcom/karnel-termux/releases/download/v$version"
|
|
84
84
|
curl -fsSL "$base/karnel-termux-install.sh" -o "$tmpdir/karnel-termux-install.sh"
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ layout: base
|
|
|
6
6
|
|
|
7
7
|
# Documentation Changelog
|
|
8
8
|
|
|
9
|
+
## 4.17.22
|
|
10
|
+
|
|
11
|
+
- **Data safety and correctness (8):** restore rejects ambiguous multiple archives; Brain preserves same-day title collisions, uses file paths for AI context, initializes/pushes `main`, detects missing `origin`, and supports editors with arguments; PostgreSQL backup filenames are path-safe; AI-session persistence escapes JSON correctly.
|
|
12
|
+
- **Agent safety (3):** positional prompts retain all words, zero-valued execution/context limits are rejected, and PLAN mode rejects shell syntax plus destructive `find`/in-place operations that bypassed its read-only contract.
|
|
13
|
+
- **Installer reliability (1):** Bun proot installation/update no longer reject safe archives before extraction.
|
|
14
|
+
- **Regression coverage:** added critical CLI contracts and strengthened Brain reciprocal-relation coverage; the isolated full suite passes.
|
|
15
|
+
- Version references bumped to `4.17.22`.
|
|
16
|
+
|
|
9
17
|
## 4.17.21
|
|
10
18
|
|
|
11
19
|
- **Official-site routing:** `karnel open`, npm package metadata, and the GitHub/npm README now point to the Vercel production site, `https://karneltermux.vercel.app`, rather than the secondary GitHub Pages mirror.
|
package/karnel/RELEASE_COMMIT
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4c9e5da4c38f3c93a5f36809265feb152fd59b27
|
|
@@ -255,10 +255,11 @@ agent_parse_args() {
|
|
|
255
255
|
AGENT_PROMPT=""
|
|
256
256
|
AGENT_SHOW_HELP=0
|
|
257
257
|
AGENT_YES=0
|
|
258
|
-
local prev=""
|
|
258
|
+
local prev="" prompt_set=0
|
|
259
|
+
local -a positional=()
|
|
259
260
|
for arg in "$@"; do
|
|
260
261
|
case "$prev" in
|
|
261
|
-
-p | --prompt) AGENT_PROMPT="$arg"; prev=""; continue ;;
|
|
262
|
+
-p | --prompt) AGENT_PROMPT="$arg"; prompt_set=1; prev=""; continue ;;
|
|
262
263
|
-m | --model) AGENT_MODEL="$arg"; prev=""; continue ;;
|
|
263
264
|
-u | --endpoint) AGENT_ENDPOINT="$arg"; prev=""; continue ;;
|
|
264
265
|
-t | --temperature) AGENT_TEMPERATURE="$arg"; prev=""; continue ;;
|
|
@@ -267,7 +268,7 @@ agent_parse_args() {
|
|
|
267
268
|
--max-tokens) AGENT_MAX_TOKENS="$arg"; prev=""; continue ;;
|
|
268
269
|
esac
|
|
269
270
|
case "$arg" in
|
|
270
|
-
-p=* | --prompt=*) AGENT_PROMPT="${arg#*=}" ;;
|
|
271
|
+
-p=* | --prompt=*) AGENT_PROMPT="${arg#*=}"; prompt_set=1 ;;
|
|
271
272
|
-m=* | --model=*) AGENT_MODEL="${arg#*=}" ;;
|
|
272
273
|
-u=* | --endpoint=*) AGENT_ENDPOINT="${arg#*=}" ;;
|
|
273
274
|
-t=* | --temperature=*) AGENT_TEMPERATURE="${arg#*=}" ;;
|
|
@@ -280,9 +281,12 @@ agent_parse_args() {
|
|
|
280
281
|
-y | --yes) AGENT_YES=1 ;;
|
|
281
282
|
-h | --help) AGENT_SHOW_HELP=1 ;;
|
|
282
283
|
-*) log_warn "Unknown option: $arg" ;;
|
|
283
|
-
*)
|
|
284
|
+
*) positional+=("$arg") ;;
|
|
284
285
|
esac
|
|
285
286
|
done
|
|
287
|
+
if (( ! prompt_set && ${#positional[@]} )); then
|
|
288
|
+
AGENT_PROMPT="${positional[*]}"
|
|
289
|
+
fi
|
|
286
290
|
}
|
|
287
291
|
readonly AGENT_ASK_SYSTEM="You are a concise chat assistant. Reply in the SAME LANGUAGE the user writes in. Be direct and practical; use markdown, and put code in fenced blocks. <attached_file path=\"...\">...</attached_file> blocks hold real file contents (action=\"no-read\" = path only). <compacted_summary>...</compacted_summary> blocks are condensed summaries of earlier turns — treat them as ground truth."
|
|
288
292
|
|
|
@@ -846,4 +850,4 @@ agent_run_loop() {
|
|
|
846
850
|
_ems=$(agent_timer_ms)
|
|
847
851
|
echo
|
|
848
852
|
printf ' %s[context %s]%s\n' "$D_GRAY" "$(agent_context_footer "$AGENT_REPL_HISTORY" "$_ems")" "$NC"
|
|
849
|
-
}
|
|
853
|
+
}
|
|
@@ -349,7 +349,7 @@ brain_init() {
|
|
|
349
349
|
log_info "Creating private repo: ${D_CYAN}$gh_user/$repo_name${D_NC}..."
|
|
350
350
|
if gh repo create "$repo_name" --private &>/dev/null; then
|
|
351
351
|
(
|
|
352
|
-
git -C "$BRAIN_DIR" init &>/dev/null
|
|
352
|
+
git -C "$BRAIN_DIR" init -b main &>/dev/null || exit 1
|
|
353
353
|
echo "# Karnel Brain" >"$BRAIN_DIR/README.md"
|
|
354
354
|
git -C "$BRAIN_DIR" add -A
|
|
355
355
|
git -C "$BRAIN_DIR" commit -m "init brain" &>/dev/null
|
|
@@ -474,6 +474,10 @@ brain_save() {
|
|
|
474
474
|
# ── Write file ──
|
|
475
475
|
mkdir -p "$BRAIN_DIR/$category"
|
|
476
476
|
local filepath="$BRAIN_DIR/$category/${date_prefix}_${slug}.md"
|
|
477
|
+
if [[ -e "$filepath" ]]; then
|
|
478
|
+
log_error "A memory with this title already exists today: ${date_prefix}_${slug}.md"
|
|
479
|
+
return 1
|
|
480
|
+
fi
|
|
477
481
|
|
|
478
482
|
{
|
|
479
483
|
_brain_frontmatter "$title" "$tags_formatted" "$category" "$related_str"
|
|
@@ -576,6 +580,10 @@ brain_add() {
|
|
|
576
580
|
|
|
577
581
|
mkdir -p "$BRAIN_DIR/$category"
|
|
578
582
|
local filepath="$BRAIN_DIR/$category/${date_prefix}_${slug}.md"
|
|
583
|
+
if [[ -e "$filepath" ]]; then
|
|
584
|
+
log_error "A memory with this title already exists today: ${date_prefix}_${slug}.md"
|
|
585
|
+
return 1
|
|
586
|
+
fi
|
|
579
587
|
|
|
580
588
|
{
|
|
581
589
|
_brain_frontmatter "$title" "$tags_formatted" "$category" ""
|
|
@@ -928,7 +936,7 @@ brain_sync() {
|
|
|
928
936
|
local branch
|
|
929
937
|
branch=$(git -C "$BRAIN_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
930
938
|
|
|
931
|
-
if ! git -C "$BRAIN_DIR" remote -
|
|
939
|
+
if ! git -C "$BRAIN_DIR" remote get-url origin &>/dev/null; then
|
|
932
940
|
log_info "No remote configured — local commit only"
|
|
933
941
|
list_item "Set up GitHub sync with: ${D_CYAN}karnel brain init${D_NC}"
|
|
934
942
|
separator
|
|
@@ -1231,22 +1239,18 @@ brain_edit() {
|
|
|
1231
1239
|
return 0
|
|
1232
1240
|
fi
|
|
1233
1241
|
|
|
1234
|
-
local
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
editor_bin="${editor_bin%% *}"
|
|
1238
|
-
if command -v "$editor_bin" &>/dev/null; then
|
|
1239
|
-
editor="${EDITOR:-nano}"
|
|
1240
|
-
elif [[ -z "$editor" ]] || ! command -v "$editor" &>/dev/null; then
|
|
1242
|
+
local -a editor_cmd=()
|
|
1243
|
+
read -r -a editor_cmd <<< "${EDITOR:-nano}"
|
|
1244
|
+
if ! command -v "${editor_cmd[0]}" &>/dev/null; then
|
|
1241
1245
|
for e in vim nano vi; do
|
|
1242
1246
|
if command -v "$e" &>/dev/null; then
|
|
1243
|
-
|
|
1247
|
+
editor_cmd=("$e")
|
|
1244
1248
|
break
|
|
1245
1249
|
fi
|
|
1246
1250
|
done
|
|
1247
1251
|
fi
|
|
1248
1252
|
|
|
1249
|
-
if [[ -
|
|
1253
|
+
if [[ ${#editor_cmd[@]} -eq 0 ]] || ! command -v "${editor_cmd[0]}" &>/dev/null; then
|
|
1250
1254
|
log_error "No editor found"
|
|
1251
1255
|
list_item "Install one: ${D_CYAN}karnel install editor${D_NC}"
|
|
1252
1256
|
return 1
|
|
@@ -1255,7 +1259,7 @@ brain_edit() {
|
|
|
1255
1259
|
echo
|
|
1256
1260
|
log_info "Opening: ${D_CYAN}$(_brain_title "$file")${D_NC}"
|
|
1257
1261
|
|
|
1258
|
-
"$
|
|
1262
|
+
"${editor_cmd[@]}" "$file"
|
|
1259
1263
|
|
|
1260
1264
|
log_success "Memory updated"
|
|
1261
1265
|
echo
|
|
@@ -1543,7 +1547,7 @@ EOF
|
|
|
1543
1547
|
if [[ -n "$f" ]]; then
|
|
1544
1548
|
matching_files+=("$f")
|
|
1545
1549
|
fi
|
|
1546
|
-
done < <(_brain_rg -i "$clean_query" | head -n 5 || true)
|
|
1550
|
+
done < <(_brain_rg -l -i "$clean_query" | head -n 5 || true)
|
|
1547
1551
|
|
|
1548
1552
|
if [[ ${#matching_files[@]} -eq 0 ]]; then
|
|
1549
1553
|
local -a words=($clean_query)
|
|
@@ -1562,7 +1566,7 @@ EOF
|
|
|
1562
1566
|
if [[ -n "$f" ]]; then
|
|
1563
1567
|
matching_files+=("$f")
|
|
1564
1568
|
fi
|
|
1565
|
-
done < <(_brain_rg -i -e "$word_regex" | head -n 5 || true)
|
|
1569
|
+
done < <(_brain_rg -l -i -e "$word_regex" | head -n 5 || true)
|
|
1566
1570
|
fi
|
|
1567
1571
|
fi
|
|
1568
1572
|
|
|
@@ -420,7 +420,11 @@ PY
|
|
|
420
420
|
ts_raw=$(echo "$session" | cut -d'|' -f2)
|
|
421
421
|
name=$(echo "$session" | cut -d'|' -f3-)
|
|
422
422
|
local escaped_name
|
|
423
|
-
escaped_name=$
|
|
423
|
+
escaped_name=${name//\\/\\\\}
|
|
424
|
+
escaped_name=${escaped_name//\"/\\\"}
|
|
425
|
+
escaped_name=${escaped_name//$'\n'/\\n}
|
|
426
|
+
escaped_name=${escaped_name//$'\r'/\\r}
|
|
427
|
+
escaped_name=${escaped_name//$'\t'/\\t}
|
|
424
428
|
if [[ $first -eq 1 ]]; then
|
|
425
429
|
first=0
|
|
426
430
|
else
|
|
@@ -514,7 +514,10 @@ pg_backup() {
|
|
|
514
514
|
log_info "Creating compressed backup for '$db_name'..."
|
|
515
515
|
local backup_dir="$KARNEL_DATA/pg_backups"
|
|
516
516
|
mkdir -p "$backup_dir"
|
|
517
|
-
local
|
|
517
|
+
local safe_db_name
|
|
518
|
+
safe_db_name=$(printf '%s' "$db_name" | tr -c '[:alnum:]_.-' '_')
|
|
519
|
+
[[ -n "$safe_db_name" ]] || { log_error "Invalid database name"; return 1; }
|
|
520
|
+
local file_name="${safe_db_name}_$(date +%Y%m%d_%H%M%S).backup.gz"
|
|
518
521
|
local file_path="$backup_dir/$file_name"
|
|
519
522
|
|
|
520
523
|
if loading "Running backup dump" _run_backup_cmd "$db_name" "$file_path"; then
|
|
@@ -522,7 +525,7 @@ pg_backup() {
|
|
|
522
525
|
log_success "Backup created successfully:"
|
|
523
526
|
list_item "$file_path"
|
|
524
527
|
list_item "Checksum generated: $(cat "${file_path}.sha256")"
|
|
525
|
-
_pg_cleanup_old_backups "$
|
|
528
|
+
_pg_cleanup_old_backups "$safe_db_name"
|
|
526
529
|
else
|
|
527
530
|
log_error "Failed to create backup"
|
|
528
531
|
rm -f "$file_path"
|
|
@@ -107,7 +107,9 @@ voice_main() {
|
|
|
107
107
|
fi
|
|
108
108
|
fi
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
local -a editor_cmd=()
|
|
111
|
+
read -r -a editor_cmd <<< "${EDITOR:-nano}"
|
|
112
|
+
if ! command -v "${editor_cmd[0]}" &>/dev/null && [[ "$skip_edit" == "false" ]]; then
|
|
111
113
|
log_warn "${EDITOR:-nano} not installed — falling back to --raw mode"
|
|
112
114
|
skip_edit=true
|
|
113
115
|
fi
|
|
@@ -154,7 +156,7 @@ voice_main() {
|
|
|
154
156
|
tmpfile="$(mktemp "${TMPDIR:-${KARNEL_CACHE:-$HOME/.cache/karnel}}/karnel-XXXXXX")"
|
|
155
157
|
echo "$raw" >"$tmpfile"
|
|
156
158
|
$is_text || log_info "Review the prompt in ${EDITOR:-nano}, fix mistakes, then save and quit"
|
|
157
|
-
${
|
|
159
|
+
"${editor_cmd[@]}" "$tmpfile" </dev/tty >/dev/tty || true
|
|
158
160
|
prompt="$(< "$tmpfile")"
|
|
159
161
|
rm -f "$tmpfile"
|
|
160
162
|
elif [[ "$skip_edit" == "false" ]] && ! { [[ -t 0 ]] && [[ -t 1 ]]; }; then
|
|
@@ -294,7 +294,7 @@ _install_bun_proot_impl() {
|
|
|
294
294
|
cd /tmp &&
|
|
295
295
|
curl -fsSL "$1" -o bun.zip &&
|
|
296
296
|
[ "$(sha256sum bun.zip | awk "{print \$1}")" = "$2" ] &&
|
|
297
|
-
unzip -l bun.zip | awk "NR>3{print \$NF}" | grep -Eq "(\\.\\./|^\\.\\./|^/)"
|
|
297
|
+
if unzip -l bun.zip | awk "NR>3{print \$NF}" | grep -Eq "(\\.\\./|^\\.\\./|^/)"; then echo "Refusing Bun archive with unsafe paths" >&2; exit 1; fi &&
|
|
298
298
|
unzip -o bun.zip >/dev/null 2>&1 &&
|
|
299
299
|
mkdir -p /usr/local/bin &&
|
|
300
300
|
mv bun-linux-aarch64/bun /usr/local/bin/bun &&
|
|
@@ -408,7 +408,7 @@ _update_bun_proot() {
|
|
|
408
408
|
cd /tmp &&
|
|
409
409
|
curl -fsSL "$1" -o bun.zip &&
|
|
410
410
|
[ "$(sha256sum bun.zip | awk "{print \$1}")" = "$2" ] &&
|
|
411
|
-
unzip -l bun.zip | awk "NR>3{print \$NF}" | grep -Eq "(\\.\\./|^\\.\\./|^/)"
|
|
411
|
+
if unzip -l bun.zip | awk "NR>3{print \$NF}" | grep -Eq "(\\.\\./|^\\.\\./|^/)"; then echo "Refusing Bun archive with unsafe paths" >&2; exit 1; fi &&
|
|
412
412
|
unzip -o bun.zip >/dev/null 2>&1 &&
|
|
413
413
|
mkdir -p /usr/local/bin &&
|
|
414
414
|
mv bun-linux-aarch64/bun /usr/local/bin/bun &&
|
|
@@ -551,11 +551,19 @@ _agent_cmd_is_readonly() {
|
|
|
551
551
|
# commands. Anything the write-gate flags, or any command not in this
|
|
552
552
|
# allowlist (ruby/php/lua/tclsh/expect/sbcl/...), is blocked — so PLAN
|
|
553
553
|
# mode can never modify the machine or run arbitrary code.
|
|
554
|
+
# Shell syntax makes a string-level allowlist unsafe: quoted command names,
|
|
555
|
+
# separators, substitutions and redirects can hide writes from the first token.
|
|
556
|
+
case "$1" in
|
|
557
|
+
*';'* | *'&'* | *'|'* | *"'"* | *'"'* | *'`'* | *'\'* | *'<'* | *'>'* | *'$'*) return 1 ;;
|
|
558
|
+
esac
|
|
554
559
|
_agent_cmd_is_write "$1" && return 1
|
|
555
560
|
local first
|
|
556
561
|
first=$(printf '%s\n' "$1" | sed 's/^[[:space:]]*//' | cut -d' ' -f1)
|
|
557
562
|
case "$first" in
|
|
558
563
|
ls | cat | head | tail | grep | rg | find | stat | wc | file | diff | sort | uniq | cut | tr | tree | du | df | which | type | echo | printf | jq | readlink | realpath | date | pwd | whoami | uname | sed)
|
|
564
|
+
if [[ " $1 " == *" -delete "* || " $1 " == *" -exec "* || " $1 " == *" -execdir "* || " $1 " == *" -ok "* || " $1 " == *" -okdir "* || " $1 " == *" -fprint "* || " $1 " == *" -fls "* || " $1 " == *" -i "* ]]; then
|
|
565
|
+
return 1
|
|
566
|
+
fi
|
|
559
567
|
return 0
|
|
560
568
|
;;
|
|
561
569
|
git)
|
|
@@ -50,10 +50,10 @@ agent_config_load() {
|
|
|
50
50
|
# Reject tampered config (defense-in-depth): numeric/boolean fields
|
|
51
51
|
# must match the expected shape or they are reset to safe defaults.
|
|
52
52
|
[[ "$AGENT_TEMPERATURE" =~ ^[0-9]+(\.[0-9]+)?$ ]] || AGENT_TEMPERATURE=0.3
|
|
53
|
-
[[ "$AGENT_MAX_TOKENS" =~ ^[0-9]
|
|
54
|
-
[[ "$AGENT_MAX_ITERATIONS" =~ ^[0-9]
|
|
53
|
+
[[ "$AGENT_MAX_TOKENS" =~ ^[1-9][0-9]*$ ]] || AGENT_MAX_TOKENS=2048
|
|
54
|
+
[[ "$AGENT_MAX_ITERATIONS" =~ ^[1-9][0-9]*$ ]] || AGENT_MAX_ITERATIONS=12
|
|
55
55
|
[[ "$AGENT_CONFIRM_COMMANDS" =~ ^[01]$ ]] || AGENT_CONFIRM_COMMANDS=1
|
|
56
|
-
[[ "$AGENT_CONTEXT_WINDOW" =~ ^[0-9]
|
|
56
|
+
[[ "$AGENT_CONTEXT_WINDOW" =~ ^[1-9][0-9]*$ ]] || AGENT_CONTEXT_WINDOW=8192
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
# ------------------------------------------------------------
|
|
@@ -89,16 +89,16 @@ agent_config_set() {
|
|
|
89
89
|
[[ "$value" =~ ^[0-9]+(\.[0-9]+)?$ ]] || { log_error "temperature must be a number (e.g. 0.3)"; return 1; }
|
|
90
90
|
AGENT_TEMPERATURE="$value" ;;
|
|
91
91
|
max_tokens | maxtokens)
|
|
92
|
-
[[ "$value" =~ ^[0-9]
|
|
92
|
+
[[ "$value" =~ ^[1-9][0-9]*$ ]] || { log_error "max_tokens must be a positive integer"; return 1; }
|
|
93
93
|
AGENT_MAX_TOKENS="$value" ;;
|
|
94
94
|
iterations | max_iterations)
|
|
95
|
-
[[ "$value" =~ ^[0-9]
|
|
95
|
+
[[ "$value" =~ ^[1-9][0-9]*$ ]] || { log_error "iterations must be a positive integer"; return 1; }
|
|
96
96
|
AGENT_MAX_ITERATIONS="$value" ;;
|
|
97
97
|
confirm_commands | confirm)
|
|
98
98
|
[[ "$value" =~ ^[01]$ ]] || { log_error "confirm_commands must be 0 or 1"; return 1; }
|
|
99
99
|
AGENT_CONFIRM_COMMANDS="$value" ;;
|
|
100
100
|
context_window | context)
|
|
101
|
-
[[ "$value" =~ ^[0-9]
|
|
101
|
+
[[ "$value" =~ ^[1-9][0-9]*$ ]] || { log_error "context_window must be a positive integer"; return 1; }
|
|
102
102
|
AGENT_CONTEXT_WINDOW="$value" ;;
|
|
103
103
|
server_command | server) AGENT_SERVER_CMD="$value" ;;
|
|
104
104
|
workspace)
|
|
@@ -363,4 +363,4 @@ agent_history_ensure_system() {
|
|
|
363
363
|
agent_history_drop_system() {
|
|
364
364
|
local history="$1"
|
|
365
365
|
echo "$history" | jq -c '[.[] | select(.role != "system")]'
|
|
366
|
-
}
|
|
366
|
+
}
|
package/package.json
CHANGED