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