karnel-termux 0.0.1

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 (258) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -0
  3. package/install.sh +272 -0
  4. package/karnel/bin/karnel +77 -0
  5. package/karnel/cli/commands/brain.sh +1553 -0
  6. package/karnel/cli/commands/cleanup.sh +66 -0
  7. package/karnel/cli/commands/doctor.sh +1528 -0
  8. package/karnel/cli/commands/env.sh +233 -0
  9. package/karnel/cli/commands/help.sh +8 -0
  10. package/karnel/cli/commands/ia.sh +502 -0
  11. package/karnel/cli/commands/init.sh +1274 -0
  12. package/karnel/cli/commands/install.sh +718 -0
  13. package/karnel/cli/commands/list.sh +399 -0
  14. package/karnel/cli/commands/open.sh +106 -0
  15. package/karnel/cli/commands/pg.sh +719 -0
  16. package/karnel/cli/commands/reinstall.sh +663 -0
  17. package/karnel/cli/commands/show.sh +90 -0
  18. package/karnel/cli/commands/start.sh +54 -0
  19. package/karnel/cli/commands/status.sh +96 -0
  20. package/karnel/cli/commands/uninstall.sh +668 -0
  21. package/karnel/cli/commands/update.sh +723 -0
  22. package/karnel/cli/commands/upgrade.sh +40 -0
  23. package/karnel/cli/commands/version.sh +3 -0
  24. package/karnel/cli/commands/voice.sh +230 -0
  25. package/karnel/cli/karnel.sh +706 -0
  26. package/karnel/modules/ai.sh +135 -0
  27. package/karnel/modules/auto.sh +114 -0
  28. package/karnel/modules/db.sh +132 -0
  29. package/karnel/modules/deploy.sh +74 -0
  30. package/karnel/modules/dev.sh +148 -0
  31. package/karnel/modules/editor.sh +115 -0
  32. package/karnel/modules/lang.sh +124 -0
  33. package/karnel/modules/npm.sh +146 -0
  34. package/karnel/modules/shell.sh +338 -0
  35. package/karnel/modules/ui.sh +139 -0
  36. package/karnel/modules/voice.sh +118 -0
  37. package/karnel/tools/ai/all.sh +209 -0
  38. package/karnel/tools/ai/antigravity-cli/README.md +45 -0
  39. package/karnel/tools/ai/antigravity-cli/bin/agy +28 -0
  40. package/karnel/tools/ai/antigravity-cli/helper/agy_helper.c +39 -0
  41. package/karnel/tools/ai/antigravity-cli/install.sh +469 -0
  42. package/karnel/tools/ai/claude-code/README.md +45 -0
  43. package/karnel/tools/ai/claude-code/bin/claude +29 -0
  44. package/karnel/tools/ai/claude-code/helper/claude_helper.c +42 -0
  45. package/karnel/tools/ai/claude-code/install.sh +278 -0
  46. package/karnel/tools/ai/cline/install.sh +160 -0
  47. package/karnel/tools/ai/codegraph/README.md +43 -0
  48. package/karnel/tools/ai/codegraph/bin/codegraph +5 -0
  49. package/karnel/tools/ai/codegraph/install.sh +160 -0
  50. package/karnel/tools/ai/codex/README.md +44 -0
  51. package/karnel/tools/ai/codex/install.sh +104 -0
  52. package/karnel/tools/ai/command-code/README.md +62 -0
  53. package/karnel/tools/ai/command-code/install.sh +136 -0
  54. package/karnel/tools/ai/crush/README.md +17 -0
  55. package/karnel/tools/ai/crush/install.sh +89 -0
  56. package/karnel/tools/ai/ctx7/README.md +26 -0
  57. package/karnel/tools/ai/ctx7/install.sh +61 -0
  58. package/karnel/tools/ai/engram/README.md +44 -0
  59. package/karnel/tools/ai/engram/install.sh +127 -0
  60. package/karnel/tools/ai/freebuff/README.md +52 -0
  61. package/karnel/tools/ai/freebuff/bin/freebuff +28 -0
  62. package/karnel/tools/ai/freebuff/helper/freebuff_helper.c +47 -0
  63. package/karnel/tools/ai/freebuff/install.sh +303 -0
  64. package/karnel/tools/ai/gemini-cli/README.md +46 -0
  65. package/karnel/tools/ai/gemini-cli/install.sh +111 -0
  66. package/karnel/tools/ai/gentle-ai/README.md +42 -0
  67. package/karnel/tools/ai/gentle-ai/install.sh +253 -0
  68. package/karnel/tools/ai/gentle-ai/termux-patches.go +256 -0
  69. package/karnel/tools/ai/gga/README.md +49 -0
  70. package/karnel/tools/ai/gga/install.sh +134 -0
  71. package/karnel/tools/ai/hermes-agent/README.md +50 -0
  72. package/karnel/tools/ai/hermes-agent/install.sh +120 -0
  73. package/karnel/tools/ai/kilocode-cli/README.md +45 -0
  74. package/karnel/tools/ai/kilocode-cli/bin/kilocode +21 -0
  75. package/karnel/tools/ai/kilocode-cli/helper/kilocode_helper.c +47 -0
  76. package/karnel/tools/ai/kilocode-cli/install.sh +179 -0
  77. package/karnel/tools/ai/kimchi-code/README.md +53 -0
  78. package/karnel/tools/ai/kimchi-code/install.sh +221 -0
  79. package/karnel/tools/ai/kimi-code/README.md +45 -0
  80. package/karnel/tools/ai/kimi-code/install.sh +111 -0
  81. package/karnel/tools/ai/kiro/README.md +17 -0
  82. package/karnel/tools/ai/kiro/install.sh +134 -0
  83. package/karnel/tools/ai/mimocode/README.md +43 -0
  84. package/karnel/tools/ai/mimocode/bin/mimo +28 -0
  85. package/karnel/tools/ai/mimocode/helper/mimocode_helper.c +47 -0
  86. package/karnel/tools/ai/mimocode/install.sh +287 -0
  87. package/karnel/tools/ai/minimax-cli/README.md +45 -0
  88. package/karnel/tools/ai/minimax-cli/install.sh +111 -0
  89. package/karnel/tools/ai/mistral-vibe/README.md +46 -0
  90. package/karnel/tools/ai/mistral-vibe/install.sh +119 -0
  91. package/karnel/tools/ai/odysseus/README.md +35 -0
  92. package/karnel/tools/ai/odysseus/install.sh +198 -0
  93. package/karnel/tools/ai/ollama/README.md +44 -0
  94. package/karnel/tools/ai/ollama/install.sh +78 -0
  95. package/karnel/tools/ai/omni-route/README.md +111 -0
  96. package/karnel/tools/ai/omni-route/install.sh +165 -0
  97. package/karnel/tools/ai/openclaude/README.md +46 -0
  98. package/karnel/tools/ai/openclaude/install.sh +110 -0
  99. package/karnel/tools/ai/openclaw/README.md +47 -0
  100. package/karnel/tools/ai/openclaw/install.sh +112 -0
  101. package/karnel/tools/ai/opencode/README.md +51 -0
  102. package/karnel/tools/ai/opencode/bin/opencode +28 -0
  103. package/karnel/tools/ai/opencode/helper/opencode_helper.c +57 -0
  104. package/karnel/tools/ai/opencode/install.sh +290 -0
  105. package/karnel/tools/ai/openspec/README.md +34 -0
  106. package/karnel/tools/ai/openspec/install.sh +61 -0
  107. package/karnel/tools/ai/pi/README.md +46 -0
  108. package/karnel/tools/ai/pi/install.sh +104 -0
  109. package/karnel/tools/ai/qwen-code/README.md +45 -0
  110. package/karnel/tools/ai/qwen-code/install.sh +111 -0
  111. package/karnel/tools/auto/all.sh +57 -0
  112. package/karnel/tools/auto/n8n/README.md +47 -0
  113. package/karnel/tools/auto/n8n/install.sh +102 -0
  114. package/karnel/tools/db/all.sh +61 -0
  115. package/karnel/tools/db/mariadb/README.md +43 -0
  116. package/karnel/tools/db/mariadb/install.sh +65 -0
  117. package/karnel/tools/db/mongodb/README.md +42 -0
  118. package/karnel/tools/db/mongodb/install.sh +73 -0
  119. package/karnel/tools/db/postgresql/README.md +45 -0
  120. package/karnel/tools/db/postgresql/install.sh +65 -0
  121. package/karnel/tools/db/redis/README.md +46 -0
  122. package/karnel/tools/db/redis/install.sh +64 -0
  123. package/karnel/tools/db/sqlite/README.md +44 -0
  124. package/karnel/tools/db/sqlite/install.sh +65 -0
  125. package/karnel/tools/deploy/all.sh +43 -0
  126. package/karnel/tools/deploy/netlify/install.sh +41 -0
  127. package/karnel/tools/deploy/railway/install.sh +109 -0
  128. package/karnel/tools/deploy/vercel/install.sh +43 -0
  129. package/karnel/tools/dev/all.sh +77 -0
  130. package/karnel/tools/dev/bat/README.md +44 -0
  131. package/karnel/tools/dev/bat/install.sh +82 -0
  132. package/karnel/tools/dev/bc/README.md +44 -0
  133. package/karnel/tools/dev/bc/install.sh +82 -0
  134. package/karnel/tools/dev/cloudflared/README.md +43 -0
  135. package/karnel/tools/dev/cloudflared/install.sh +82 -0
  136. package/karnel/tools/dev/curl/README.md +43 -0
  137. package/karnel/tools/dev/curl/install.sh +82 -0
  138. package/karnel/tools/dev/fzf/README.md +44 -0
  139. package/karnel/tools/dev/fzf/install.sh +82 -0
  140. package/karnel/tools/dev/gh/README.md +43 -0
  141. package/karnel/tools/dev/gh/install.sh +82 -0
  142. package/karnel/tools/dev/html2text/README.md +44 -0
  143. package/karnel/tools/dev/html2text/install.sh +82 -0
  144. package/karnel/tools/dev/imagemagick/README.md +44 -0
  145. package/karnel/tools/dev/imagemagick/install.sh +82 -0
  146. package/karnel/tools/dev/jq/README.md +44 -0
  147. package/karnel/tools/dev/jq/install.sh +82 -0
  148. package/karnel/tools/dev/lsd/README.md +44 -0
  149. package/karnel/tools/dev/lsd/install.sh +82 -0
  150. package/karnel/tools/dev/make/README.md +44 -0
  151. package/karnel/tools/dev/make/install.sh +82 -0
  152. package/karnel/tools/dev/ncurses/README.md +42 -0
  153. package/karnel/tools/dev/ncurses/install.sh +82 -0
  154. package/karnel/tools/dev/openssh/README.md +43 -0
  155. package/karnel/tools/dev/openssh/install.sh +75 -0
  156. package/karnel/tools/dev/proot/README.md +43 -0
  157. package/karnel/tools/dev/proot/install.sh +82 -0
  158. package/karnel/tools/dev/shfmt/README.md +44 -0
  159. package/karnel/tools/dev/shfmt/install.sh +82 -0
  160. package/karnel/tools/dev/tmate/README.md +44 -0
  161. package/karnel/tools/dev/tmate/install.sh +82 -0
  162. package/karnel/tools/dev/tmux/README.md +43 -0
  163. package/karnel/tools/dev/tmux/install.sh +75 -0
  164. package/karnel/tools/dev/translate/README.md +44 -0
  165. package/karnel/tools/dev/translate/install.sh +82 -0
  166. package/karnel/tools/dev/tree/README.md +44 -0
  167. package/karnel/tools/dev/tree/install.sh +82 -0
  168. package/karnel/tools/dev/udocker/README.md +44 -0
  169. package/karnel/tools/dev/udocker/install.sh +82 -0
  170. package/karnel/tools/dev/wget/README.md +44 -0
  171. package/karnel/tools/dev/wget/install.sh +82 -0
  172. package/karnel/tools/editor/all.sh +59 -0
  173. package/karnel/tools/editor/code-server/README.md +42 -0
  174. package/karnel/tools/editor/code-server/install.sh +74 -0
  175. package/karnel/tools/editor/neovim/README.md +43 -0
  176. package/karnel/tools/editor/neovim/install.sh +64 -0
  177. package/karnel/tools/editor/nvchad/README.md +45 -0
  178. package/karnel/tools/editor/nvchad/install.sh +105 -0
  179. package/karnel/tools/lang/all.sh +63 -0
  180. package/karnel/tools/lang/clang/README.md +43 -0
  181. package/karnel/tools/lang/clang/install.sh +79 -0
  182. package/karnel/tools/lang/golang/README.md +43 -0
  183. package/karnel/tools/lang/golang/install.sh +79 -0
  184. package/karnel/tools/lang/nodejs/README.md +43 -0
  185. package/karnel/tools/lang/nodejs/install.sh +79 -0
  186. package/karnel/tools/lang/perl/README.md +43 -0
  187. package/karnel/tools/lang/perl/install.sh +79 -0
  188. package/karnel/tools/lang/php/README.md +43 -0
  189. package/karnel/tools/lang/php/install.sh +79 -0
  190. package/karnel/tools/lang/python/README.md +43 -0
  191. package/karnel/tools/lang/python/install.sh +79 -0
  192. package/karnel/tools/lang/rust/README.md +43 -0
  193. package/karnel/tools/lang/rust/install.sh +79 -0
  194. package/karnel/tools/npm/all.sh +90 -0
  195. package/karnel/tools/npm/live-server/README.md +43 -0
  196. package/karnel/tools/npm/live-server/install.sh +96 -0
  197. package/karnel/tools/npm/localtunnel/README.md +43 -0
  198. package/karnel/tools/npm/localtunnel/install.sh +112 -0
  199. package/karnel/tools/npm/markserv/README.md +43 -0
  200. package/karnel/tools/npm/markserv/install.sh +95 -0
  201. package/karnel/tools/npm/ncu/README.md +43 -0
  202. package/karnel/tools/npm/ncu/install.sh +95 -0
  203. package/karnel/tools/npm/nestjs/README.md +43 -0
  204. package/karnel/tools/npm/nestjs/install.sh +95 -0
  205. package/karnel/tools/npm/ngrok/README.md +44 -0
  206. package/karnel/tools/npm/ngrok/install.sh +95 -0
  207. package/karnel/tools/npm/prettier/README.md +43 -0
  208. package/karnel/tools/npm/prettier/install.sh +95 -0
  209. package/karnel/tools/npm/psqlformat/README.md +43 -0
  210. package/karnel/tools/npm/psqlformat/install.sh +95 -0
  211. package/karnel/tools/npm/turbopack/README.md +8 -0
  212. package/karnel/tools/npm/turbopack/install.sh +42 -0
  213. package/karnel/tools/npm/typescript/README.md +43 -0
  214. package/karnel/tools/npm/typescript/install.sh +95 -0
  215. package/karnel/tools/npm/vercel/README.md +43 -0
  216. package/karnel/tools/npm/vercel/install.sh +95 -0
  217. package/karnel/tools/shell/all.sh +67 -0
  218. package/karnel/tools/shell/better-npm/README.md +43 -0
  219. package/karnel/tools/shell/better-npm/install.sh +85 -0
  220. package/karnel/tools/shell/fzf-tab/README.md +44 -0
  221. package/karnel/tools/shell/fzf-tab/install.sh +85 -0
  222. package/karnel/tools/shell/history-substring/README.md +43 -0
  223. package/karnel/tools/shell/history-substring/install.sh +85 -0
  224. package/karnel/tools/shell/powerlevel10k/README.md +43 -0
  225. package/karnel/tools/shell/powerlevel10k/install.sh +85 -0
  226. package/karnel/tools/shell/you-should-use/README.md +43 -0
  227. package/karnel/tools/shell/you-should-use/install.sh +85 -0
  228. package/karnel/tools/shell/zsh-autopair/README.md +44 -0
  229. package/karnel/tools/shell/zsh-autopair/install.sh +85 -0
  230. package/karnel/tools/shell/zsh-autosuggestions/README.md +43 -0
  231. package/karnel/tools/shell/zsh-autosuggestions/install.sh +85 -0
  232. package/karnel/tools/shell/zsh-completions/README.md +43 -0
  233. package/karnel/tools/shell/zsh-completions/install.sh +85 -0
  234. package/karnel/tools/shell/zsh-defer/README.md +43 -0
  235. package/karnel/tools/shell/zsh-defer/install.sh +85 -0
  236. package/karnel/tools/shell/zsh-syntax-highlighting/README.md +43 -0
  237. package/karnel/tools/shell/zsh-syntax-highlighting/install.sh +72 -0
  238. package/karnel/tools/ui/all.sh +61 -0
  239. package/karnel/tools/ui/banner/README.md +42 -0
  240. package/karnel/tools/ui/banner/install.sh +160 -0
  241. package/karnel/tools/ui/cursor/README.md +42 -0
  242. package/karnel/tools/ui/cursor/install.sh +58 -0
  243. package/karnel/tools/ui/extra-keys/README.md +43 -0
  244. package/karnel/tools/ui/extra-keys/install.sh +62 -0
  245. package/karnel/tools/ui/font/README.md +43 -0
  246. package/karnel/tools/ui/font/install.sh +63 -0
  247. package/karnel/tools/voice/all.sh +27 -0
  248. package/karnel/tools/voice/install.sh +10 -0
  249. package/karnel/tools/voice/uninstall.sh +8 -0
  250. package/karnel/utils/banner.sh +492 -0
  251. package/karnel/utils/bootstrap.sh +25 -0
  252. package/karnel/utils/colors.sh +33 -0
  253. package/karnel/utils/dialogrc +30 -0
  254. package/karnel/utils/env.sh +53 -0
  255. package/karnel/utils/log.sh +567 -0
  256. package/karnel/utils/version.sh +205 -0
  257. package/package.json +42 -0
  258. package/scripts/npm-install.js +60 -0
@@ -0,0 +1,1553 @@
1
+ #!/usr/bin/env bash
2
+
3
+ import "@/utils/log"
4
+ import "@/utils/colors"
5
+
6
+ readonly BRAIN_DIR="$KARNEL_DATA/brain"
7
+
8
+ # ── Helpers ──────────────────────────────────────────────────
9
+
10
+ _brain_ensure() {
11
+ if [[ ! -d "$BRAIN_DIR" ]]; then
12
+ log_error "Brain not initialized"
13
+ list_item "Run: ${D_CYAN}karnel brain init${D_NC}"
14
+ return 1
15
+ fi
16
+ }
17
+
18
+ _brain_slug() {
19
+ local title="$1"
20
+ local slug
21
+ slug=$(echo "$title" \
22
+ | tr '[:upper:]' '[:lower:]' \
23
+ | sed -E 's/ç/c/g; s/ã|â|á|à/a/g; s/é|ê|è/e/g; s/í|ì/i/g; s/ó|ô|õ|ò/o/g; s/ú|ù/u/g; s/[^a-z0-9]+/-/g' \
24
+ | sed -E 's/^-+|-+$//g' \
25
+ | cut -c1-60)
26
+ echo "$slug"
27
+ }
28
+
29
+ _brain_frontmatter() {
30
+ local title="$1" tags="$2" category="$3" related="$4"
31
+ cat <<-EOF
32
+ ---
33
+ title: $title
34
+ tags: [$tags]
35
+ created: $(date +%Y-%m-%d)
36
+ category: $category
37
+ EOF
38
+ if [[ -n "$related" ]]; then
39
+ echo "related: [$related]"
40
+ fi
41
+ echo "---"
42
+ }
43
+
44
+ _brain_title() {
45
+ local file="$1"
46
+ local t
47
+ t=$(grep -m1 '^title: ' "$file" 2>/dev/null | sed 's/^title: //')
48
+ if [[ -n "$t" ]]; then
49
+ echo "$t"
50
+ else
51
+ local fallback
52
+ fallback=$(grep -m1 '^# ' "$file" 2>/dev/null | sed 's/^# //')
53
+ echo "${fallback:-$(basename "$file" .md)}"
54
+ fi
55
+ }
56
+
57
+ _brain_update_related() {
58
+ local file="$1" slug="$2"
59
+ local tmp
60
+ tmp=$(mktemp)
61
+ local frontmatter_end
62
+ frontmatter_end=$(awk '/^---$/ {++n} n==2 {print NR; exit}' "$file")
63
+
64
+ if [[ -z "$frontmatter_end" ]]; then
65
+ rm -f "$tmp"
66
+ return
67
+ fi
68
+
69
+ head -n $((frontmatter_end - 1)) "$file" >"$tmp"
70
+
71
+ if grep -q "related:.*$slug" "$tmp" 2>/dev/null; then
72
+ rm -f "$tmp"
73
+ return
74
+ fi
75
+
76
+ if grep -q "^related:" "$tmp" 2>/dev/null; then
77
+ sed -i "s/^related: \[\(.*\)\]/related: [\1, $slug]/" "$tmp"
78
+ else
79
+ sed -i "/^---$/a related: [$slug]" "$tmp"
80
+ fi
81
+ tail -n +$((frontmatter_end)) "$file" >>"$tmp"
82
+ mv "$tmp" "$file"
83
+ }
84
+
85
+ _brain_remove_related() {
86
+ local file="$1" slug="$2"
87
+ local tmp
88
+ tmp=$(mktemp)
89
+ local frontmatter_end
90
+ frontmatter_end=$(awk '/^---$/ {++n} n==2 {print NR; exit}' "$file")
91
+
92
+ if [[ -z "$frontmatter_end" ]]; then
93
+ rm -f "$tmp"
94
+ return
95
+ fi
96
+
97
+ head -n $((frontmatter_end - 1)) "$file" >"$tmp"
98
+ sed -i "s/related: \[\(.*\)$slug\(.*\)\]/related: [\1\2]/" "$tmp"
99
+ sed -i 's/related: \[, */related: [/; s/, *, */, /g; s/related: \[ *\]//' "$tmp"
100
+ tail -n +$((frontmatter_end)) "$file" >>"$tmp"
101
+ mv "$tmp" "$file"
102
+ }
103
+
104
+ _brain_pick_file() {
105
+ local prompt="$1"
106
+ local -a files=()
107
+
108
+ while IFS= read -r f; do
109
+ files+=("$f")
110
+ done < <(find "$BRAIN_DIR" -name "*.md" ! -name "README.md" 2>/dev/null | sort)
111
+
112
+ if [[ ${#files[@]} -eq 0 ]]; then
113
+ echo ""
114
+ return
115
+ fi
116
+
117
+ local idx=0
118
+ for f in "${files[@]}"; do
119
+ local t
120
+ t=$(_brain_title "$f")
121
+ printf " ${D_GREEN}%2d.${D_NC} %s\n" $((idx + 1)) "$t" >&2
122
+ ((idx++))
123
+ done
124
+
125
+ echo >&2
126
+ read_input "$prompt" pick
127
+
128
+ if [[ -z "$pick" ]]; then
129
+ echo ""
130
+ return
131
+ fi
132
+
133
+ local selected=""
134
+ if [[ "$pick" =~ ^[0-9]+$ ]] && ((pick >= 1 && pick <= ${#files[@]})); then
135
+ selected="${files[$((pick - 1))]}"
136
+ else
137
+ for f in "${files[@]}"; do
138
+ local t
139
+ t=$(_brain_title "$f")
140
+ if echo "$t" | grep -qi "$pick"; then
141
+ selected="$f"
142
+ break
143
+ fi
144
+ done
145
+ fi
146
+
147
+ echo "$selected"
148
+ }
149
+
150
+ _brain_editor() {
151
+ local title="$1"
152
+
153
+ if ! command -v ${EDITOR:-nano} &>/dev/null; then
154
+ log_error "${EDITOR:-nano} not found"
155
+ list_item "Install it: ${D_CYAN}karnel install editor${D_NC}"
156
+ return 1
157
+ fi
158
+
159
+ local tmpfile
160
+ tmpfile=$(mktemp)
161
+
162
+ echo "# $title" >"$tmpfile"
163
+ echo >>"$tmpfile"
164
+ echo "- " >>"$tmpfile"
165
+
166
+ ${EDITOR:-nano} "$tmpfile" </dev/tty >/dev/tty
167
+
168
+ local lines
169
+ lines=$(wc -l <"$tmpfile")
170
+ if ((lines <= 2)); then
171
+ rm -f "$tmpfile"
172
+ return 1
173
+ fi
174
+
175
+ echo "$tmpfile"
176
+ }
177
+
178
+ # ── Help ────────────────────────────────────────────────────
179
+
180
+ brain_help() {
181
+ echo
182
+ box "Karnel Brain — Your Second Brain"
183
+ echo
184
+ log_info "Usage: karnel brain <subcommand> [options]"
185
+ echo
186
+ separator_section "Subcommands"
187
+ echo
188
+ printf " ${D_CYAN}%-12s${NC} %s\n" "init" "Initialize brain directory and GitHub repo"
189
+ printf " ${D_CYAN}%-12s${NC} %s\n" "save" "Save a new memory interactively"
190
+ printf " ${D_CYAN}%-12s${NC} %s\n" "search" "Search memories by keywords or tags"
191
+ printf " ${D_CYAN}%-12s${NC} %s\n" "ls" "List memories by category"
192
+ printf " ${D_CYAN}%-12s${NC} %s\n" "edit" "Edit a memory in your \$EDITOR"
193
+ printf " ${D_CYAN}%-12s${NC} %s\n" "delete" "Delete a memory permanently"
194
+ printf " ${D_CYAN}%-12s${NC} %s\n" "reset" "Destroy the entire brain"
195
+ printf " ${D_CYAN}%-12s${NC} %s\n" "show" "View a memory with its relations"
196
+ printf " ${D_CYAN}%-12s${NC} %s\n" "graph" "Visual map of all connections"
197
+ printf " ${D_CYAN}%-12s${NC} %s\n" "skill" "Create an AI skill from memories"
198
+ printf " ${D_CYAN}%-12s${NC} %s\n" "relate" "Link two memories"
199
+ printf " ${D_CYAN}%-12s${NC} %s\n" "sync" "Push/pull to GitHub private repo"
200
+ echo
201
+ separator_section "Examples"
202
+ echo
203
+ printf " ${D_CYAN}karnel brain init${NC} # Create local brain\n"
204
+ printf " ${D_CYAN}karnel brain save${NC} # Interactive save\n"
205
+ printf " ${D_CYAN}karnel brain search react${NC} # Search all memories\n"
206
+ printf " ${D_CYAN}karnel brain ls${NC} # List all categories\n"
207
+ printf " ${D_CYAN}karnel brain ls react${NC} # List react category\n"
208
+ printf " ${D_CYAN}karnel brain edit${NC} # Interactive edit\n"
209
+ printf " ${D_CYAN}karnel brain edit slug-name${NC} # Edit by slug\n"
210
+ printf " ${D_CYAN}karnel brain delete${NC} # Interactive delete\n"
211
+ printf " ${D_CYAN}karnel brain reset${NC} # Destroy local brain\n"
212
+ printf " ${D_CYAN}karnel brain show slug-name${NC} # View memory + relations\n"
213
+ printf " ${D_CYAN}karnel brain graph${NC} # Show connection map\n"
214
+ printf " ${D_CYAN}karnel brain skill${NC} # Create a skill from memories\n"
215
+ printf " ${D_CYAN}karnel brain sync${NC} # Sync with GitHub\n"
216
+ echo
217
+ }
218
+
219
+ # ── Init ────────────────────────────────────────────────────
220
+
221
+ brain_init() {
222
+ separator
223
+ box "Initialize Karnel Brain"
224
+ separator
225
+ echo
226
+
227
+ if [[ -d "$BRAIN_DIR" ]]; then
228
+ log_warn "Brain already exists at: ${D_CYAN}$BRAIN_DIR${D_NC}"
229
+ separator
230
+ return 0
231
+ fi
232
+
233
+ local gh_ok=false
234
+ if command -v gh &>/dev/null && gh auth status &>/dev/null; then
235
+ gh_ok=true
236
+ fi
237
+
238
+ if ! $gh_ok; then
239
+ mkdir -p "$BRAIN_DIR" 2>/dev/null
240
+ log_success "Local brain created: ${D_CYAN}$BRAIN_DIR${D_NC}"
241
+ list_item "Install gh for GitHub sync: ${D_CYAN}karnel install dev${D_NC}"
242
+ separator
243
+ return 0
244
+ fi
245
+
246
+ local gh_user
247
+ gh_user=$(gh api user --jq '.login' 2>/dev/null)
248
+ local repo_name="karnel-brain"
249
+
250
+ if gh repo view "$gh_user/$repo_name" &>/dev/null; then
251
+ log_info "Found existing brain repo: ${D_CYAN}$gh_user/$repo_name${D_NC}"
252
+ echo
253
+ read_confirm "Clone it to restore your memories?" confirm
254
+ if [[ "$confirm" == "y" ]]; then
255
+ loading "Cloning repository..." gh repo clone "$gh_user/$repo_name" "$BRAIN_DIR"
256
+ log_success "Brain restored from GitHub"
257
+ separator
258
+ return 0
259
+ fi
260
+ fi
261
+
262
+ local orig_dir="$PWD"
263
+ mkdir -p "$BRAIN_DIR"
264
+ log_success "Local brain created: ${D_CYAN}$BRAIN_DIR${D_NC}"
265
+ echo
266
+
267
+ read_confirm "Create a private GitHub repo for your brain?" confirm
268
+ if [[ "$confirm" != "y" ]]; then
269
+ log_info "Skipping GitHub setup. You can set it up later."
270
+ separator
271
+ return 0
272
+ fi
273
+
274
+ log_info "Creating private repo: ${D_CYAN}$gh_user/$repo_name${D_NC}..."
275
+ if gh repo create "$repo_name" --private &>/dev/null; then
276
+ (
277
+ cd "$BRAIN_DIR" || return 1
278
+ git init &>/dev/null
279
+ echo "# Karnel Brain" >README.md
280
+ git add -A
281
+ git commit -m "init brain" &>/dev/null
282
+ git remote add origin "https://github.com/$gh_user/$repo_name.git"
283
+ loading "Pushing to GitHub..." git push -u origin main
284
+ )
285
+ log_success "Repo created and linked: ${D_CYAN}https://github.com/$gh_user/$repo_name${D_NC}"
286
+ else
287
+ log_warn "Failed to create repo. Check your permissions."
288
+ fi
289
+
290
+ separator
291
+ }
292
+
293
+ # ── Save ────────────────────────────────────────────────────
294
+
295
+ brain_save() {
296
+ _brain_ensure || return 1
297
+
298
+ separator
299
+ box "Save a New Memory"
300
+ separator
301
+ echo
302
+
303
+ read_input "Title" title
304
+ if [[ -z "$title" ]]; then
305
+ log_error "Title cannot be empty"
306
+ separator
307
+ return 1
308
+ fi
309
+
310
+ local slug
311
+ slug=$(_brain_slug "$title")
312
+ local date_prefix
313
+ date_prefix=$(date +%Y-%m-%d)
314
+
315
+ # ── Categories ──
316
+ local categories
317
+ categories=$(find "$BRAIN_DIR" -mindepth 1 -maxdepth 1 -type d ! -name ".git" -exec basename {} \; 2>/dev/null | sort)
318
+ echo
319
+ if [[ -n "$categories" ]]; then
320
+ log_info "Existing categories:"
321
+ echo
322
+ while IFS= read -r cat; do
323
+ printf " ${D_GREEN}•${D_NC} ${D_CYAN}%s${D_NC}\n" "$cat"
324
+ done <<<"$categories"
325
+ echo
326
+ fi
327
+ read_input "Category" category
328
+ if [[ -z "$category" ]]; then
329
+ category="general"
330
+ fi
331
+ category=$(_brain_slug "$category")
332
+
333
+ # ── Tags ──
334
+ read_input "Tags (comma separated)" tags_input
335
+ tags_input="${tags_input:-}"
336
+ local tags_formatted
337
+ tags_formatted=$(echo "$tags_input" | sed 's/, */, /g' | sed 's/^, \|, $//g')
338
+
339
+ # ── Content via editor ──
340
+ local tmpfile
341
+ tmpfile=$(_brain_editor "$title")
342
+ if [[ -z "$tmpfile" ]]; then
343
+ log_error "Content cannot be empty"
344
+ return 1
345
+ fi
346
+ local content
347
+ content=$(cat "$tmpfile")
348
+ rm -f "$tmpfile"
349
+
350
+ # ── Suggest relations ──
351
+ local -a related_slugs=()
352
+ if [[ -n "$tags_formatted" ]]; then
353
+ local IFS=','
354
+ for tag in $tags_formatted; do
355
+ tag=$(echo "$tag" | xargs)
356
+ while IFS= read -r match; do
357
+ local match_slug
358
+ match_slug=$(basename "$match" .md)
359
+ # Don't relate to self and avoid duplicates
360
+ if [[ "$match_slug" != "${date_prefix}_${slug}" ]]; then
361
+ local already=false
362
+ for r in "${related_slugs[@]}"; do
363
+ [[ "$r" == "$match_slug" ]] && already=true && break
364
+ done
365
+ $already || related_slugs+=("$match_slug")
366
+ fi
367
+ done < <(rg -l -i "tags:.*$tag" "$BRAIN_DIR" 2>/dev/null || true)
368
+ done
369
+ IFS=$' \t\n'
370
+
371
+ if [[ ${#related_slugs[@]} -gt 0 ]]; then
372
+ echo
373
+ log_info "Found ${#related_slugs[@]} related memory(ies) with shared tags:"
374
+ for r in "${related_slugs[@]}"; do
375
+ local rfile
376
+ rfile=$(find "$BRAIN_DIR" -name "${r}.md" 2>/dev/null | head -1)
377
+ local r_title
378
+ r_title=$(_brain_title "$rfile")
379
+ printf " ${D_GREEN}•${D_NC} %s\n" "${r_title:-$r}"
380
+ done
381
+ read_confirm "Link these relations?" link_confirm
382
+ if [[ "$link_confirm" != "y" ]]; then
383
+ related_slugs=()
384
+ fi
385
+ fi
386
+ fi
387
+
388
+ # ── Build related string ──
389
+ local related_str=""
390
+ if [[ ${#related_slugs[@]} -gt 0 ]]; then
391
+ related_str=$(IFS=,; echo "${related_slugs[*]}")
392
+ fi
393
+
394
+ # ── Write file ──
395
+ mkdir -p "$BRAIN_DIR/$category"
396
+ local filepath="$BRAIN_DIR/$category/${date_prefix}_${slug}.md"
397
+
398
+ {
399
+ _brain_frontmatter "$title" "$tags_formatted" "$category" "$related_str"
400
+ echo
401
+ echo "$content"
402
+ } >"$filepath"
403
+
404
+ # ── Write reverse relations ──
405
+ if [[ -n "$related_str" ]]; then
406
+ local IFS=','
407
+ for rslug in $related_str; do
408
+ rslug=$(echo "$rslug" | xargs)
409
+ local rfile
410
+ rfile=$(find "$BRAIN_DIR" -name "${rslug}.md" 2>/dev/null | head -1)
411
+ if [[ -n "$rfile" ]]; then
412
+ _brain_update_related "$rfile" "${date_prefix}_${slug}"
413
+ fi
414
+ done
415
+ IFS=$' \t\n'
416
+ fi
417
+
418
+ echo
419
+ log_success "Memory saved to ${D_CYAN}$category/${date_prefix}_${slug}.md${D_NC}"
420
+
421
+ if command -v glow &>/dev/null; then
422
+ read_confirm "Preview with glow?" preview
423
+ if [[ "$preview" == "y" ]]; then
424
+ glow "$filepath"
425
+ fi
426
+ fi
427
+
428
+ echo
429
+ }
430
+
431
+ # ── Search ──────────────────────────────────────────────────
432
+
433
+ brain_search() {
434
+ _brain_ensure || return 1
435
+
436
+ local query="$*"
437
+ if [[ -z "$query" ]]; then
438
+ read_input "Search query" query
439
+ fi
440
+ if [[ -z "$query" ]]; then
441
+ return 0
442
+ fi
443
+
444
+ separator
445
+ box "Search: $query"
446
+ separator
447
+ echo
448
+
449
+ if ! command -v rg &>/dev/null; then
450
+ log_error "ripgrep not found — this shouldn't happen"
451
+ return 1
452
+ fi
453
+
454
+ local -a results=()
455
+ while IFS=':' read -r file line content; do
456
+ results+=("$file|$line|$content")
457
+ done < <(rg -n -i "$query" "$BRAIN_DIR" --glob '*.md' --no-heading 2>/dev/null || true)
458
+
459
+ if [[ ${#results[@]} -eq 0 ]]; then
460
+ log_warn "No results for: $query"
461
+ return 0
462
+ fi
463
+
464
+ local total=${#results[@]}
465
+ log_info "Found $total match(es):"
466
+ echo
467
+
468
+ local -a menu_files=()
469
+ local -a menu_titles=()
470
+ local idx=0
471
+ for result in "${results[@]}"; do
472
+ local f=$(echo "$result" | cut -d'|' -f1)
473
+ local ln=$(echo "$result" | cut -d'|' -f2)
474
+ local text=$(echo "$result" | cut -d'|' -f3-)
475
+ local relative="${f#$BRAIN_DIR/}"
476
+ local title
477
+ title=$(_brain_title "$f")
478
+
479
+ # Only show unique files in menu
480
+ local seen=false
481
+ for mf in "${menu_files[@]}"; do
482
+ [[ "$mf" == "$f" ]] && seen=true && break
483
+ done
484
+ if ! $seen; then
485
+ menu_files+=("$f")
486
+ menu_titles+=("$title")
487
+ printf " ${D_GREEN}%2d.${D_NC} ${D_CYAN}%s${D_NC}\n" $((idx + 1)) "$title"
488
+ ((idx++))
489
+ fi
490
+ done
491
+
492
+ echo
493
+ read_input "Open memory (# or name)" pick
494
+
495
+ if [[ -z "$pick" ]]; then
496
+ return 0
497
+ fi
498
+
499
+ local selected_file=""
500
+ if [[ "$pick" =~ ^[0-9]+$ ]] && ((pick >= 1 && pick <= ${#menu_files[@]})); then
501
+ selected_file="${menu_files[$((pick - 1))]}"
502
+ else
503
+ for i in "${!menu_titles[@]}"; do
504
+ if echo "${menu_titles[$i]}" | grep -qi "$pick"; then
505
+ selected_file="${menu_files[$i]}"
506
+ break
507
+ fi
508
+ done
509
+ fi
510
+
511
+ if [[ -z "$selected_file" ]] || [[ ! -f "$selected_file" ]]; then
512
+ log_error "Memory not found"
513
+ return 1
514
+ fi
515
+
516
+ echo
517
+ if command -v glow &>/dev/null; then
518
+ glow "$selected_file"
519
+ else
520
+ cat "$selected_file"
521
+ fi
522
+
523
+ echo
524
+ }
525
+
526
+ # ── List ─────────────────────────────────────────────────────
527
+
528
+ brain_ls() {
529
+ _brain_ensure || return 1
530
+
531
+ local filter="$*"
532
+
533
+ separator
534
+ box "Your Memories"
535
+ separator
536
+ echo
537
+
538
+ local total=0
539
+ local dirs
540
+
541
+ if [[ -n "$filter" ]]; then
542
+ dirs="$BRAIN_DIR/$filter"
543
+ if [[ ! -d "$dirs" ]]; then
544
+ log_warn "Category not found: $filter"
545
+ return 1
546
+ fi
547
+ else
548
+ dirs=$(find "$BRAIN_DIR" -mindepth 1 -maxdepth 1 -type d ! -name ".git" 2>/dev/null | sort)
549
+ if [[ -z "$dirs" ]]; then
550
+ echo -e " ${GRAY}No memories yet${D_NC}"
551
+ echo
552
+ list_item "Add one: ${D_CYAN}karnel brain save${D_NC}"
553
+ return 0
554
+ fi
555
+ fi
556
+
557
+ while IFS= read -r dir; do
558
+ local category
559
+ category=$(basename "$dir")
560
+ local files
561
+ files=$(find "$dir" -maxdepth 1 -name "*.md" ! -name "README.md" 2>/dev/null | sort -r)
562
+
563
+ if [[ -z "$files" ]]; then
564
+ continue
565
+ fi
566
+
567
+ if [[ -z "$filter" ]]; then
568
+ echo
569
+ separator_section "$category"
570
+ echo
571
+ fi
572
+
573
+ while IFS= read -r file; do
574
+ local title
575
+ title=$(_brain_title "$file")
576
+ local tags_line
577
+ tags_line=$(grep "^tags:" "$file" 2>/dev/null | sed 's/tags: \[//;s/\]//;s/, /, /g')
578
+ printf " ${D_GREEN}•${D_NC} %s\n" "$title"
579
+ if [[ -n "$tags_line" ]]; then
580
+ printf " ${D_DIM}%s${D_NC}\n" "$tags_line"
581
+ fi
582
+ ((total++))
583
+ done <<<"$files"
584
+ done <<<"$dirs"
585
+
586
+ echo
587
+ separator
588
+ list_item "$total memory(ies)"
589
+ separator
590
+ }
591
+
592
+ # ── Relate ──────────────────────────────────────────────────
593
+
594
+ brain_relate() {
595
+ _brain_ensure || return 1
596
+
597
+ local slug_a="$1"
598
+ local slug_b="$2"
599
+ local file_a="" file_b=""
600
+
601
+ if [[ -z "$slug_a" ]] || [[ -z "$slug_b" ]]; then
602
+ separator
603
+ box "Relate Memories"
604
+ separator
605
+ echo
606
+ log_info "Pick the first memory:"
607
+ echo
608
+ file_a=$(_brain_pick_file "First memory (# or name)")
609
+ [[ -z "$file_a" ]] && return 0
610
+
611
+ echo
612
+ log_info "Pick the second memory:"
613
+ echo
614
+ file_b=$(_brain_pick_file "Second memory (# or name)")
615
+ [[ -z "$file_b" ]] && return 0
616
+ else
617
+ file_a=$(find "$BRAIN_DIR" -name "${slug_a}.md" 2>/dev/null | head -1)
618
+ file_b=$(find "$BRAIN_DIR" -name "${slug_b}.md" 2>/dev/null | head -1)
619
+
620
+ if [[ -z "$file_a" ]]; then
621
+ log_error "Memory not found: $slug_a"
622
+ return 1
623
+ fi
624
+ if [[ -z "$file_b" ]]; then
625
+ log_error "Memory not found: $slug_b"
626
+ return 1
627
+ fi
628
+ fi
629
+
630
+ echo
631
+ slug_a=$(basename "$file_a" .md)
632
+ slug_b=$(basename "$file_b" .md)
633
+
634
+ _brain_update_related "$file_a" "$slug_b"
635
+ _brain_update_related "$file_b" "$slug_a"
636
+
637
+ log_success "Linked ${D_CYAN}$(_brain_title "$file_a")${D_NC} ↔ ${D_CYAN}$(_brain_title "$file_b")${D_NC}"
638
+ echo
639
+ }
640
+
641
+ # ── Show ─────────────────────────────────────────────────────
642
+
643
+ _brain_show_relations() {
644
+ local file="$1"
645
+ local related
646
+ related=$(grep "^related:" "$file" 2>/dev/null | sed 's/related: \[//;s/\]//')
647
+ [[ -z "$related" ]] && return
648
+
649
+ echo -e " ${D_GRAY}──${D_NC} ${GRAY}Related${NC}"
650
+ local IFS=','
651
+ for r in $related; do
652
+ r=$(echo "$r" | xargs)
653
+ [[ -z "$r" ]] && continue
654
+ local rfile
655
+ rfile=$(find "$BRAIN_DIR" -name "${r}.md" 2>/dev/null | head -1)
656
+ if [[ -n "$rfile" ]]; then
657
+ local rtitle
658
+ rtitle=$(_brain_title "$rfile")
659
+ printf " ${D_GREEN}•${D_NC} %s\n" "$rtitle"
660
+ fi
661
+ done
662
+ IFS=$' \t\n'
663
+ }
664
+
665
+ brain_show() {
666
+ _brain_ensure || return 1
667
+
668
+ local slug="$1"
669
+ local file=""
670
+
671
+ if [[ -n "$slug" ]]; then
672
+ file=$(find "$BRAIN_DIR" -name "${slug}.md" 2>/dev/null | head -1)
673
+ if [[ -z "$file" ]]; then
674
+ log_error "Memory not found: $slug"
675
+ return 1
676
+ fi
677
+ else
678
+ echo
679
+ separator_section "Show Memory"
680
+ echo
681
+ file=$(_brain_pick_file "Memory to show (# or name)")
682
+ [[ -z "$file" ]] && return 0
683
+ fi
684
+
685
+ echo
686
+ if command -v glow &>/dev/null; then
687
+ glow "$file"
688
+ else
689
+ cat "$file"
690
+ fi
691
+
692
+ _brain_show_relations "$file"
693
+ echo
694
+ }
695
+
696
+ # ── Graph ────────────────────────────────────────────────────
697
+
698
+ brain_graph() {
699
+ _brain_ensure || return 1
700
+
701
+ echo
702
+ separator_section "Knowledge Graph"
703
+ echo
704
+
705
+ local -a files=()
706
+ while IFS= read -r f; do
707
+ files+=("$f")
708
+ done < <(find "$BRAIN_DIR" -name "*.md" ! -name "README.md" 2>/dev/null | sort)
709
+
710
+ if [[ ${#files[@]} -eq 0 ]]; then
711
+ list_item "No memories yet"
712
+ return 0
713
+ fi
714
+
715
+ local total_edges=0
716
+ for f in "${files[@]}"; do
717
+ local title
718
+ title=$(_brain_title "$f")
719
+ local slug
720
+ slug=$(basename "$f" .md)
721
+ local category
722
+ category=$(basename "$(dirname "$f")")
723
+ local related
724
+ related=$(grep "^related:" "$f" 2>/dev/null | sed 's/related: \[//;s/\]//')
725
+
726
+ printf " ${D_CYAN}%s${NC} ${D_DIM}(%s)${D_NC}\n" "$title" "$category"
727
+
728
+ if [[ -n "$related" ]]; then
729
+ local IFS=','
730
+ for r in $related; do
731
+ r=$(echo "$r" | xargs)
732
+ [[ -z "$r" ]] && continue
733
+ local rfile
734
+ rfile=$(find "$BRAIN_DIR" -name "${r}.md" 2>/dev/null | head -1)
735
+ if [[ -n "$rfile" ]]; then
736
+ local rtitle
737
+ rtitle=$(_brain_title "$rfile")
738
+ local rcat
739
+ rcat=$(basename "$(dirname "$rfile")")
740
+ printf " ${GRAY}└──${NC} %s ${D_DIM}(%s)${D_NC}\n" "$rtitle" "$rcat"
741
+ ((total_edges++))
742
+ fi
743
+ done
744
+ IFS=$' \t\n'
745
+ else
746
+ printf " ${GRAY}└──${NC} ${D_DIM}(no relations)${D_NC}\n"
747
+ fi
748
+ echo
749
+ done
750
+
751
+ list_item "$total_edges connections across ${#files[@]} memories"
752
+ echo
753
+ }
754
+
755
+ # ── Sync ────────────────────────────────────────────────────
756
+
757
+ brain_sync() {
758
+ _brain_ensure || return 1
759
+
760
+ separator
761
+ box "Sync Brain"
762
+ separator
763
+ echo
764
+
765
+ if [[ ! -d "$BRAIN_DIR/.git" ]]; then
766
+ log_warn "Not a git repository"
767
+ list_item "Run: ${D_CYAN}karnel brain init${D_NC} to set up Git"
768
+ separator
769
+ return 1
770
+ fi
771
+
772
+ cd "$BRAIN_DIR" || return 1
773
+
774
+ if ! git remote -v &>/dev/null; then
775
+ log_info "No remote configured — local commit only"
776
+ list_item "Set up GitHub sync with: ${D_CYAN}karnel brain init${D_NC}"
777
+ separator
778
+ return 0
779
+ fi
780
+
781
+ local commit_msg="brain sync $(date +%Y-%m-%d_%H:%M)"
782
+ if [[ -n "$(git status --porcelain)" ]]; then
783
+ loading "Staging changes..." git add -A
784
+ loading "Committing changes..." git commit -m "$commit_msg"
785
+ else
786
+ log_info "No changes to commit"
787
+ fi
788
+
789
+ local branch
790
+ branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
791
+
792
+ if ! git rev-parse --abbrev-ref --symbolic-full-name @{upstream} &>/dev/null; then
793
+ loading "Pushing to origin/$branch..." git push -u origin "$branch"
794
+ if [[ $? -eq 0 ]]; then
795
+ log_success "Brain synced with GitHub"
796
+ else
797
+ log_error "Push failed"
798
+ list_item "Check your GitHub authentication: ${D_CYAN}gh auth login${D_NC}"
799
+ fi
800
+ separator
801
+ return
802
+ fi
803
+
804
+ loading "Pulling latest from remote..." git pull --rebase
805
+ local pull_exit=$?
806
+ if [[ $pull_exit -ne 0 ]]; then
807
+ log_error "Pull failed — your local and remote histories diverged"
808
+ list_item "Force push instead: ${D_CYAN}git push --force${D_NC}"
809
+ list_item "Or resolve manually in: ${D_CYAN}$BRAIN_DIR${D_NC}"
810
+ separator
811
+ return 1
812
+ fi
813
+
814
+ loading "Pushing to remote..." git push
815
+ if [[ $? -eq 0 ]]; then
816
+ log_success "Brain synced with GitHub"
817
+ else
818
+ log_error "Push failed"
819
+ list_item "Check your connection and permissions"
820
+ fi
821
+
822
+ separator
823
+ }
824
+
825
+ # ── Skill ───────────────────────────────────────────────────
826
+
827
+ brain_skill() {
828
+ _brain_ensure || return 1
829
+
830
+ local skill_name base_dir
831
+
832
+ # ── Collect all memories ──
833
+ local -a all_files=()
834
+ while IFS= read -r f; do
835
+ all_files+=("$f")
836
+ done < <(find "$BRAIN_DIR" -name "*.md" ! -name "README.md" 2>/dev/null | sort)
837
+
838
+ if [[ ${#all_files[@]} -eq 0 ]]; then
839
+ echo
840
+ separator
841
+ box "Create Skill from Memories"
842
+ separator
843
+ echo
844
+ list_item "No memories yet"
845
+ list_item "Create some: ${D_CYAN}karnel brain save${D_NC}"
846
+ separator
847
+ return 0
848
+ fi
849
+
850
+ # ── Pick memories (multi-select) ──
851
+ while true; do
852
+ echo
853
+ separator
854
+ box "Create Skill from Memories"
855
+ separator
856
+ echo
857
+
858
+ local idx=0
859
+ for f in "${all_files[@]}"; do
860
+ local t
861
+ t=$(_brain_title "$f")
862
+ printf " ${D_GREEN}%2d.${D_NC} %s\n" $((idx + 1)) "$t"
863
+ ((idx++))
864
+ done
865
+
866
+ echo
867
+ log_info "Select memories to include in the skill"
868
+ read_input "Numbers (comma-separated), 'all', or empty to cancel" pick
869
+
870
+ if [[ -z "$pick" ]]; then
871
+ return 0
872
+ fi
873
+
874
+ local -a selected=()
875
+
876
+ if [[ "$pick" == "all" ]]; then
877
+ for ((i = 0; i < ${#all_files[@]}; i++)); do
878
+ selected+=("${all_files[$i]}")
879
+ done
880
+ else
881
+ local IFS=','
882
+ for num in $pick; do
883
+ num=$(echo "$num" | xargs)
884
+ if [[ "$num" =~ ^[0-9]+$ ]] && ((num >= 1 && num <= ${#all_files[@]})); then
885
+ selected+=("${all_files[$((num - 1))]}")
886
+ fi
887
+ done
888
+ IFS=$' \t\n'
889
+ fi
890
+
891
+ # ── Expand relations ──
892
+ local -a expanded=()
893
+ local -a seen=()
894
+ for f in "${selected[@]}"; do
895
+ local slug
896
+ slug=$(basename "$f" .md)
897
+ # Avoid duplicates
898
+ local dup=false
899
+ for s in "${seen[@]}"; do
900
+ [[ "$s" == "$slug" ]] && dup=true && break
901
+ done
902
+ $dup && continue
903
+ seen+=("$slug")
904
+ expanded+=("$f")
905
+
906
+ # Add related memories
907
+ local related
908
+ related=$(grep "^related:" "$f" 2>/dev/null | sed 's/related: \[//;s/\]//')
909
+ if [[ -n "$related" ]]; then
910
+ local IFS=','
911
+ for r in $related; do
912
+ r=$(echo "$r" | xargs)
913
+ [[ -z "$r" ]] && continue
914
+ # Check dup
915
+ local rdup=false
916
+ for s in "${seen[@]}"; do
917
+ [[ "$s" == "$r" ]] && rdup=true && break
918
+ done
919
+ $rdup && continue
920
+ seen+=("$r")
921
+ local rf
922
+ rf=$(find "$BRAIN_DIR" -name "${r}.md" 2>/dev/null | head -1)
923
+ [[ -n "$rf" ]] && expanded+=("$rf")
924
+ done
925
+ IFS=$' \t\n'
926
+ fi
927
+ done
928
+
929
+ echo
930
+ log_info "Skill will include ${#expanded[@]} memories:"
931
+ for f in "${expanded[@]}"; do
932
+ local et
933
+ et=$(_brain_title "$f")
934
+ echo -e " ${D_GREEN}•${D_NC} $et"
935
+ done
936
+ echo
937
+ read_confirm "Proceed?" confirm
938
+ [[ "$confirm" == "y" ]] && break
939
+ done
940
+
941
+ # ── Skill name ──
942
+ read_input "Skill name (kebab-case, e.g. termux-setup)" skill_name
943
+ skill_name=$(echo "$skill_name" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g' | sed -E 's/^-+|-+$//g')
944
+ if [[ -z "$skill_name" ]]; then
945
+ log_error "Skill name cannot be empty"
946
+ separator
947
+ return 1
948
+ fi
949
+
950
+ # ── Global or local ──
951
+ echo
952
+ log_info "Where do you want to install the skill?"
953
+ read_confirm "Install globally (~/.agents/skills/)?" scope
954
+ if [[ "$scope" == "y" ]]; then
955
+ base_dir="$HOME/.agents/skills"
956
+ else
957
+ base_dir=".agents/skills"
958
+ fi
959
+
960
+ local skill_dir="$base_dir/$skill_name"
961
+ if [[ -d "$skill_dir" ]]; then
962
+ log_warn "Skill already exists: $skill_dir"
963
+ read_confirm "Overwrite?" confirm
964
+ [[ "$confirm" != "y" ]] && return 0
965
+ rm -rf "$skill_dir"
966
+ fi
967
+
968
+ mkdir -p "$skill_dir/memories"
969
+ log_success "Creating skill: ${D_CYAN}$skill_name${D_NC}"
970
+
971
+ # ── Generate SKILL.md ──
972
+ local description="Skill generated from brain memories"
973
+ local trigger="Use when the user asks about: "
974
+ local first=true
975
+ for f in "${expanded[@]}"; do
976
+ local ft
977
+ ft=$(_brain_title "$f")
978
+ if $first; then
979
+ trigger+="$ft"
980
+ first=false
981
+ else
982
+ trigger+=", $ft"
983
+ fi
984
+ done
985
+
986
+ {
987
+ echo "---"
988
+ echo "name: $skill_name"
989
+ echo "description: $description"
990
+ echo "trigger: $trigger"
991
+ echo "---"
992
+ echo
993
+ echo "# $skill_name"
994
+ echo
995
+ echo "Skill created from the following brain memories:"
996
+ echo
997
+ for f in "${expanded[@]}"; do
998
+ local ftag ftags
999
+ ft=$(_brain_title "$f")
1000
+ ftags=$(grep "^tags:" "$f" 2>/dev/null | sed 's/tags: \[//;s/\]//')
1001
+ echo "## $ft"
1002
+ [[ -n "$ftags" ]] && echo "Tags: $ftags"
1003
+ echo
1004
+ # Include body after frontmatter
1005
+ local in_fm=false
1006
+ while IFS= read -r line; do
1007
+ if [[ "$line" == "---" ]]; then
1008
+ if $in_fm; then in_fm=false; continue; fi
1009
+ in_fm=true; continue
1010
+ fi
1011
+ $in_fm && continue
1012
+ echo "$line"
1013
+ done <"$f" | tail -n +2
1014
+ echo
1015
+ echo "---"
1016
+ echo
1017
+ done
1018
+ } >"$skill_dir/SKILL.md"
1019
+
1020
+ # ── Link individual memory files ──
1021
+ local midx=1
1022
+ for f in "${expanded[@]}"; do
1023
+ local ft ftags frel
1024
+ ft=$(_brain_title "$f")
1025
+ ftags=$(grep "^tags:" "$f" 2>/dev/null | sed 's/tags: \[//;s/\]//')
1026
+ frel=$(grep "^related:" "$f" 2>/dev/null | sed 's/related: \[//;s/\]//')
1027
+ local fname
1028
+ fname=$(printf "%02d_%s.md" "$midx" "$(basename "$f" .md | sed 's/^[0-9-]*_//')")
1029
+ {
1030
+ echo "---"
1031
+ echo "title: $ft"
1032
+ [[ -n "$ftags" ]] && echo "tags: [$ftags]"
1033
+ [[ -n "$frel" ]] && echo "related: [$frel]"
1034
+ echo "---"
1035
+ echo
1036
+ # Copy body
1037
+ local in_fm=false
1038
+ while IFS= read -r line; do
1039
+ if [[ "$line" == "---" ]]; then
1040
+ if $in_fm; then in_fm=false; continue; fi
1041
+ in_fm=true; continue
1042
+ fi
1043
+ $in_fm && continue
1044
+ echo "$line"
1045
+ done <"$f"
1046
+ } >"$skill_dir/memories/$fname"
1047
+ ((midx++))
1048
+ done
1049
+
1050
+ log_success "Skill created: ${D_CYAN}$skill_dir${D_NC}"
1051
+ list_item "Your agent will load it automatically"
1052
+ separator
1053
+ }
1054
+
1055
+ # ── Edit ─────────────────────────────────────────────────────
1056
+
1057
+ brain_edit() {
1058
+ _brain_ensure || return 1
1059
+
1060
+ local slug="$1"
1061
+ local file=""
1062
+
1063
+ if [[ -n "$slug" ]]; then
1064
+ file=$(find "$BRAIN_DIR" -name "${slug}.md" 2>/dev/null | head -1)
1065
+ if [[ -z "$file" ]]; then
1066
+ log_error "Memory not found: $slug"
1067
+ return 1
1068
+ fi
1069
+ else
1070
+ echo
1071
+ separator_section "Edit Memory"
1072
+ echo
1073
+ file=$(_brain_pick_file "Memory to edit (# or name)")
1074
+ fi
1075
+
1076
+ if [[ -z "$file" ]]; then
1077
+ return 0
1078
+ fi
1079
+
1080
+ local editor="${EDITOR:-}"
1081
+ if command -v ${EDITOR:-nano} &>/dev/null; then
1082
+ editor="${EDITOR:-nano}"
1083
+ elif [[ -z "$editor" ]] || ! command -v "$editor" &>/dev/null; then
1084
+ for e in vim nano vi; do
1085
+ if command -v "$e" &>/dev/null; then
1086
+ editor="$e"
1087
+ break
1088
+ fi
1089
+ done
1090
+ fi
1091
+
1092
+ if [[ -z "$editor" ]]; then
1093
+ log_error "No editor found"
1094
+ list_item "Install one: ${D_CYAN}karnel install editor${D_NC}"
1095
+ return 1
1096
+ fi
1097
+
1098
+ echo
1099
+ log_info "Opening: ${D_CYAN}$(_brain_title "$file")${D_NC}"
1100
+
1101
+ "$editor" "$file"
1102
+
1103
+ log_success "Memory updated"
1104
+ echo
1105
+ }
1106
+
1107
+ # ── Delete ───────────────────────────────────────────────────
1108
+
1109
+ brain_delete() {
1110
+ _brain_ensure || return 1
1111
+
1112
+ local slug="$1"
1113
+ local file=""
1114
+
1115
+ if [[ -n "$slug" ]]; then
1116
+ file=$(find "$BRAIN_DIR" -name "${slug}.md" 2>/dev/null | head -1)
1117
+ if [[ -z "$file" ]]; then
1118
+ log_error "Memory not found: $slug"
1119
+ return 1
1120
+ fi
1121
+ else
1122
+ echo
1123
+ separator_section "Delete Memory"
1124
+ echo
1125
+ file=$(_brain_pick_file "Memory to delete (# or name)")
1126
+ fi
1127
+
1128
+ if [[ -z "$file" ]]; then
1129
+ return 0
1130
+ fi
1131
+
1132
+ local title
1133
+ title=$(_brain_title "$file")
1134
+ echo
1135
+ log_warn "Delete memory: ${D_CYAN}$title${D_NC}?"
1136
+ read_confirm "This cannot be undone" confirm
1137
+ if [[ "$confirm" != "y" ]]; then
1138
+ log_info "Cancelled"
1139
+ return 0
1140
+ fi
1141
+
1142
+ # Remove relations from related files
1143
+ local file_slug
1144
+ file_slug=$(basename "$file" .md)
1145
+ while IFS= read -r rfile; do
1146
+ _brain_remove_related "$rfile" "$file_slug"
1147
+ done < <(rg -l "$file_slug" "$BRAIN_DIR" --glob '*.md' 2>/dev/null || true)
1148
+
1149
+ rm "$file"
1150
+ log_success "Deleted: ${D_CYAN}$title${D_NC}"
1151
+
1152
+ # Clean up empty category directory
1153
+ local dir
1154
+ dir=$(dirname "$file")
1155
+ if [[ -z "$(find "$dir" -name '*.md' ! -name 'README.md' 2>/dev/null)" ]]; then
1156
+ rmdir "$dir" 2>/dev/null || true
1157
+ list_item "Empty category directory removed"
1158
+ fi
1159
+
1160
+ echo
1161
+ }
1162
+
1163
+ # ── Reset ────────────────────────────────────────────────────
1164
+
1165
+ brain_reset() {
1166
+ separator
1167
+ box "Reset Brain"
1168
+ separator
1169
+ echo
1170
+
1171
+ if [[ ! -d "$BRAIN_DIR" ]]; then
1172
+ log_warn "Brain does not exist"
1173
+ separator
1174
+ return 0
1175
+ fi
1176
+
1177
+ log_warn "This will DESTROY all local memories:"
1178
+ list_item "Location: ${D_CYAN}$BRAIN_DIR${D_NC}"
1179
+ echo
1180
+
1181
+ read_confirm "Are you sure?" confirm
1182
+ if [[ "$confirm" != "y" ]]; then
1183
+ log_info "Cancelled"
1184
+ separator
1185
+ return 0
1186
+ fi
1187
+
1188
+ echo
1189
+ loading "Deleting local brain..." rm -rf "$BRAIN_DIR"
1190
+ log_success "Brain destroyed"
1191
+ list_item "Recreate with: ${D_CYAN}karnel brain init${D_NC}"
1192
+ separator
1193
+ }
1194
+
1195
+ # ── Dashboard ────────────────────────────────────────────────
1196
+
1197
+ brain_dashboard() {
1198
+ if [[ ! -d "$BRAIN_DIR" ]]; then
1199
+ brain_help
1200
+ return
1201
+ fi
1202
+
1203
+ echo
1204
+ separator_section "Karnel Brain Dashboard"
1205
+ echo
1206
+
1207
+ local total_files
1208
+ total_files=$(find "$BRAIN_DIR" -name "*.md" ! -name "README.md" 2>/dev/null | wc -l)
1209
+ local total_categories
1210
+ total_categories=$(find "$BRAIN_DIR" -mindepth 1 -maxdepth 1 -type d ! -name ".git" 2>/dev/null | wc -l)
1211
+ local has_git=false
1212
+ [[ -d "$BRAIN_DIR/.git" ]] && has_git=true
1213
+
1214
+ printf " ${D_CYAN}%-20s${NC} ${GRAY}=${NC} %s\n" "Memories" "$total_files"
1215
+ printf " ${D_CYAN}%-20s${NC} ${GRAY}=${NC} %s\n" "Categories" "$total_categories"
1216
+ printf " ${D_CYAN}%-20s${NC} ${GRAY}=${NC} %s\n" "Git" "$($has_git && echo '✓' || echo '✗')"
1217
+ printf " ${D_CYAN}%-20s${NC} ${GRAY}=${NC} ${D_DIM}%s${D_NC}\n" "Location" "$BRAIN_DIR"
1218
+ echo
1219
+
1220
+ separator_section "Quick Start"
1221
+ echo
1222
+ list_item "Save: ${D_CYAN}karnel brain save${D_NC}"
1223
+ list_item "Show: ${D_CYAN}karnel brain show${D_NC}"
1224
+ list_item "Search: ${D_CYAN}karnel brain search${D_NC}"
1225
+ list_item "Edit: ${D_CYAN}karnel brain edit${D_NC}"
1226
+ list_item "Delete: ${D_CYAN}karnel brain delete${D_NC}"
1227
+ list_item "Reset: ${D_CYAN}karnel brain reset${D_NC}"
1228
+ list_item "Graph: ${D_CYAN}karnel brain graph${D_NC}"
1229
+ list_item "Skill: ${D_CYAN}karnel brain skill${D_NC}"
1230
+ list_item "Sync: ${D_CYAN}karnel brain sync${D_NC}"
1231
+ list_item "Ask: ${D_CYAN}karnel brain ask <pregunta>${D_NC}"
1232
+ echo
1233
+ }
1234
+
1235
+ _run_ai_query() {
1236
+ local ai_cmd="$1"
1237
+ local full_prompt="$2"
1238
+ local default_model="$3"
1239
+ local out_file="$4"
1240
+
1241
+ if [[ "$ai_cmd" == "gemini" ]]; then
1242
+ gemini -p "$full_prompt" >"$out_file" 2>&1
1243
+ elif [[ "$ai_cmd" == "opencode" ]]; then
1244
+ opencode run "$full_prompt" >"$out_file" 2>&1
1245
+ elif [[ "$ai_cmd" == "claude" ]]; then
1246
+ claude -p "$full_prompt" >"$out_file" 2>&1
1247
+ elif [[ "$ai_cmd" == "ollama" ]]; then
1248
+ local model="$default_model"
1249
+ if [[ -z "$model" ]]; then
1250
+ model=$(ollama list 2>/dev/null | awk 'NR==2 {print $1}')
1251
+ fi
1252
+ if [[ -n "$model" ]]; then
1253
+ ollama run "$model" "$full_prompt" >"$out_file" 2>&1
1254
+ else
1255
+ echo "Ollama is installed but no local models found. Run: ollama pull llama3" >"$out_file"
1256
+ return 1
1257
+ fi
1258
+ fi
1259
+ }
1260
+
1261
+ brain_ai_config() {
1262
+ local config_file="$KARNEL_CONFIG/ai_config.json"
1263
+ mkdir -p "$KARNEL_CONFIG"
1264
+ if [[ ! -f "$config_file" ]]; then
1265
+ cat >"$config_file" <<'EOF'
1266
+ {
1267
+ "provider": "auto",
1268
+ "default_model": "",
1269
+ "context_max_size": 4000,
1270
+ "cache_enabled": true,
1271
+ "only_memories": false
1272
+ }
1273
+ EOF
1274
+ fi
1275
+
1276
+ if [[ $# -eq 0 ]]; then
1277
+ separator
1278
+ box "Brain AI Configuration"
1279
+ separator
1280
+ echo
1281
+ if command -v jq &>/dev/null; then
1282
+ jq . "$config_file"
1283
+ else
1284
+ cat "$config_file"
1285
+ fi
1286
+ echo
1287
+ log_info "Configure variables using: karnel brain config <key> <value>"
1288
+ list_item "Keys: provider, default_model, context_max_size, cache_enabled, only_memories"
1289
+ separator
1290
+ return 0
1291
+ fi
1292
+
1293
+ local key="$1"
1294
+ local val="$2"
1295
+
1296
+ if [[ -z "$val" ]]; then
1297
+ log_error "Value required. Usage: karnel brain config <key> <value>"
1298
+ return 1
1299
+ fi
1300
+
1301
+ if command -v jq &>/dev/null; then
1302
+ local temp=$(mktemp)
1303
+ if [[ "$val" == "true" ]] || [[ "$val" == "false" ]]; then
1304
+ jq --argjson v "$val" ".${key} = \$v" "$config_file" > "$temp" && mv "$temp" "$config_file"
1305
+ elif [[ "$val" =~ ^[0-9]+$ ]]; then
1306
+ jq --argjson v "$val" ".${key} = \$v" "$config_file" > "$temp" && mv "$temp" "$config_file"
1307
+ else
1308
+ jq --arg v "$val" ".${key} = \$v" "$config_file" > "$temp" && mv "$temp" "$config_file"
1309
+ fi
1310
+ log_success "Updated config: $key = $val"
1311
+ else
1312
+ log_error "jq is required to modify config file"
1313
+ return 1
1314
+ fi
1315
+ }
1316
+
1317
+ brain_ask() {
1318
+ _brain_ensure || return 1
1319
+
1320
+ local only_memories_flag=false
1321
+ local clean_query=""
1322
+ for arg in "$@"; do
1323
+ if [[ "$arg" == "--only-memories" ]] || [[ "$arg" == "-m" ]]; then
1324
+ only_memories_flag=true
1325
+ else
1326
+ if [[ -z "$clean_query" ]]; then
1327
+ clean_query="$arg"
1328
+ else
1329
+ clean_query="$clean_query $arg"
1330
+ fi
1331
+ fi
1332
+ done
1333
+
1334
+ if [[ -z "$clean_query" ]]; then
1335
+ read_input "Ask a question about your memories" clean_query
1336
+ fi
1337
+ if [[ -z "$clean_query" ]]; then
1338
+ return 0
1339
+ fi
1340
+
1341
+ # Load config
1342
+ local config_file="$KARNEL_CONFIG/ai_config.json"
1343
+ mkdir -p "$KARNEL_CONFIG"
1344
+ if [[ ! -f "$config_file" ]]; then
1345
+ cat >"$config_file" <<'EOF'
1346
+ {
1347
+ "provider": "auto",
1348
+ "default_model": "",
1349
+ "context_max_size": 4000,
1350
+ "cache_enabled": true,
1351
+ "only_memories": false
1352
+ }
1353
+ EOF
1354
+ fi
1355
+
1356
+ local provider="auto"
1357
+ local default_model=""
1358
+ local context_max_size=4000
1359
+ local cache_enabled="true"
1360
+ local default_only_memories="false"
1361
+
1362
+ if command -v jq &>/dev/null; then
1363
+ provider=$(jq -r '.provider // "auto"' "$config_file" 2>/dev/null)
1364
+ default_model=$(jq -r '.default_model // ""' "$config_file" 2>/dev/null)
1365
+ context_max_size=$(jq -r '.context_max_size // 4000' "$config_file" 2>/dev/null)
1366
+ cache_enabled=$(jq -r '.cache_enabled // true' "$config_file" 2>/dev/null)
1367
+ default_only_memories=$(jq -r '.only_memories // false' "$config_file" 2>/dev/null)
1368
+ fi
1369
+
1370
+ if [[ "$default_only_memories" == "true" ]]; then
1371
+ only_memories_flag=true
1372
+ fi
1373
+
1374
+ local -a matching_files=()
1375
+ while IFS= read -r f; do
1376
+ if [[ -n "$f" ]]; then
1377
+ matching_files+=("$f")
1378
+ fi
1379
+ done < <(rg -i -l "$clean_query" "$BRAIN_DIR" --glob '*.md' 2>/dev/null | head -n 5 || true)
1380
+
1381
+ if [[ ${#matching_files[@]} -eq 0 ]]; then
1382
+ local -a words=($clean_query)
1383
+ local word_regex=""
1384
+ for w in "${words[@]}"; do
1385
+ if [[ ${#w} -gt 3 ]]; then
1386
+ if [[ -z "$word_regex" ]]; then
1387
+ word_regex="$w"
1388
+ else
1389
+ word_regex="$word_regex|$w"
1390
+ fi
1391
+ fi
1392
+ done
1393
+ if [[ -n "$word_regex" ]]; then
1394
+ while IFS= read -r f; do
1395
+ if [[ -n "$f" ]]; then
1396
+ matching_files+=("$f")
1397
+ fi
1398
+ done < <(rg -i -l -e "$word_regex" "$BRAIN_DIR" --glob '*.md' 2>/dev/null | head -n 5 || true)
1399
+ fi
1400
+ fi
1401
+
1402
+ local context=""
1403
+ if [[ ${#matching_files[@]} -gt 0 ]]; then
1404
+ context="Here is some context from my personal memories:\n\n"
1405
+ for f in "${matching_files[@]}"; do
1406
+ local title
1407
+ title=$(_brain_title "$f")
1408
+ local content
1409
+ content=$(cat "$f")
1410
+
1411
+ if (( ${#context} + ${#content} > context_max_size )); then
1412
+ local available=$(( context_max_size - ${#context} ))
1413
+ if [[ $available -gt 50 ]]; then
1414
+ content="${content:0:$available}...\n(truncated due to context limit)"
1415
+ context="${context}--- MEMORY: ${title} ---\n${content}\n\n"
1416
+ fi
1417
+ break
1418
+ else
1419
+ context="${context}--- MEMORY: ${title} ---\n${content}\n\n"
1420
+ fi
1421
+ done
1422
+ fi
1423
+
1424
+ # Auto detect provider prioritizing local offline
1425
+ local ai_cmd=""
1426
+ if [[ "$provider" == "auto" ]]; then
1427
+ if command -v opencode &>/dev/null; then
1428
+ ai_cmd="opencode"
1429
+ elif command -v ollama &>/dev/null; then
1430
+ ai_cmd="ollama"
1431
+ elif command -v gemini &>/dev/null; then
1432
+ ai_cmd="gemini"
1433
+ elif command -v claude &>/dev/null; then
1434
+ ai_cmd="claude"
1435
+ fi
1436
+ else
1437
+ ai_cmd="$provider"
1438
+ fi
1439
+
1440
+ if [[ -z "$ai_cmd" ]]; then
1441
+ log_warn "No AI assistant found or selected to process the query."
1442
+ log_info "Selected provider: $provider"
1443
+ log_info "Please install an AI assistant first (e.g. karnel install ai --opencode)"
1444
+ return 1
1445
+ fi
1446
+
1447
+ local system_prompt=""
1448
+ if [[ "$only_memories_flag" == "true" ]]; then
1449
+ system_prompt="INSTRUÇÃO CRÍTICA: Responda a pergunta do usuário baseando-se UNICAMENTE e EXCLUSIVAMENTE nas memórias fornecidas acima. Se as memórias não contiverem a informação necessária para responder à pergunta, responda exatamente com: 'Não encontrei nenhuma memória relacionada a isso.' e nada mais. Não invente nenhuma informação fora do contexto.\n\n"
1450
+ fi
1451
+
1452
+ local cache_dir="$KARNEL_CACHE/brain_ai_cache"
1453
+ mkdir -p "$cache_dir"
1454
+ local cache_key
1455
+ cache_key=$(echo -n "${only_memories_flag}_${context}_${clean_query}" | sha256sum | cut -d' ' -f1)
1456
+ local cache_file="$cache_dir/${cache_key}.txt"
1457
+
1458
+ if [[ "$cache_enabled" == "true" ]] && [[ -f "$cache_file" ]]; then
1459
+ log_success "Response retrieved from cache (offline):"
1460
+ echo
1461
+ cat "$cache_file"
1462
+ echo
1463
+ return 0
1464
+ fi
1465
+
1466
+ local full_prompt="${context}${system_prompt}Based on the context, answer the following question: ${clean_query}"
1467
+ log_info "Asking AI ($ai_cmd) with brain context..."
1468
+ echo
1469
+
1470
+ local ai_response_temp
1471
+ ai_response_temp=$(mktemp)
1472
+
1473
+ if loading "Generating AI response" _run_ai_query "$ai_cmd" "$full_prompt" "$default_model" "$ai_response_temp"; then
1474
+ echo
1475
+ cat "$ai_response_temp"
1476
+ echo
1477
+ if [[ "$cache_enabled" == "true" ]]; then
1478
+ cp "$ai_response_temp" "$cache_file"
1479
+ fi
1480
+ else
1481
+ echo
1482
+ log_error "AI query failed:"
1483
+ cat "$ai_response_temp"
1484
+ echo
1485
+ rm -f "$ai_response_temp"
1486
+ return 1
1487
+ fi
1488
+ rm -f "$ai_response_temp"
1489
+ }
1490
+
1491
+ # ── Main dispatcher ──────────────────────────────────────────
1492
+
1493
+ brain_main() {
1494
+ local cmd="$1"
1495
+ shift || true
1496
+
1497
+ case "$cmd" in
1498
+ init)
1499
+ brain_init
1500
+ ;;
1501
+ save)
1502
+ brain_save
1503
+ ;;
1504
+ search)
1505
+ brain_search "$@"
1506
+ ;;
1507
+ ask)
1508
+ brain_ask "$@"
1509
+ ;;
1510
+ config | ai-config)
1511
+ brain_ai_config "$@"
1512
+ ;;
1513
+ ls | list)
1514
+ brain_ls "$@"
1515
+ ;;
1516
+ edit)
1517
+ brain_edit "$@"
1518
+ ;;
1519
+ delete | rm)
1520
+ brain_delete "$@"
1521
+ ;;
1522
+ reset | destroy)
1523
+ brain_reset
1524
+ ;;
1525
+ relate)
1526
+ brain_relate "$@"
1527
+ ;;
1528
+ show | view)
1529
+ brain_show "$@"
1530
+ ;;
1531
+ dashboard | dash | stats)
1532
+ brain_dashboard
1533
+ ;;
1534
+ graph | map)
1535
+ brain_graph
1536
+ ;;
1537
+ skill | skills)
1538
+ brain_skill
1539
+ ;;
1540
+ sync)
1541
+ brain_sync
1542
+ ;;
1543
+ "" | --help | -h)
1544
+ brain_help
1545
+ ;;
1546
+ *)
1547
+ log_error "Unknown subcommand: $cmd"
1548
+ echo
1549
+ brain_help
1550
+ return 1
1551
+ ;;
1552
+ esac
1553
+ }