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.
- package/LICENSE +21 -0
- package/README.md +1137 -0
- package/core/bin/core +85 -0
- package/core/bin/omni +21 -0
- package/core/cli/commands/--version.sh +8 -0
- package/core/cli/commands/brain.sh +1549 -0
- package/core/cli/commands/doctor.sh +834 -0
- package/core/cli/commands/env.sh +226 -0
- package/core/cli/commands/init.sh +1266 -0
- package/core/cli/commands/install.sh +759 -0
- package/core/cli/commands/list.sh +397 -0
- package/core/cli/commands/open.sh +71 -0
- package/core/cli/commands/pg.sh +761 -0
- package/core/cli/commands/reinstall.sh +694 -0
- package/core/cli/commands/show.sh +90 -0
- package/core/cli/commands/uninstall.sh +700 -0
- package/core/cli/commands/update.sh +754 -0
- package/core/cli/commands/voice.sh +170 -0
- package/core/cli/core.sh +82 -0
- package/core/cli/omni.sh +634 -0
- package/core/modules/ai.sh +152 -0
- package/core/modules/auto.sh +90 -0
- package/core/modules/db.sh +94 -0
- package/core/modules/deploy.sh +50 -0
- package/core/modules/dev.sh +126 -0
- package/core/modules/editor.sh +101 -0
- package/core/modules/lang.sh +100 -0
- package/core/modules/npm.sh +122 -0
- package/core/modules/shell.sh +312 -0
- package/core/modules/ui.sh +115 -0
- package/core/tools/ai/all.sh +63 -0
- package/core/tools/ai/antigravity-cli/README.md +45 -0
- package/core/tools/ai/antigravity-cli/bin/agy +28 -0
- package/core/tools/ai/antigravity-cli/helper/agy_helper.c +39 -0
- package/core/tools/ai/antigravity-cli/install.sh +469 -0
- package/core/tools/ai/claude-code/README.md +45 -0
- package/core/tools/ai/claude-code/bin/claude +29 -0
- package/core/tools/ai/claude-code/helper/claude_helper.c +42 -0
- package/core/tools/ai/claude-code/install.sh +278 -0
- package/core/tools/ai/codegraph/README.md +43 -0
- package/core/tools/ai/codegraph/bin/codegraph +3 -0
- package/core/tools/ai/codegraph/install.sh +149 -0
- package/core/tools/ai/codex/README.md +44 -0
- package/core/tools/ai/codex/install.sh +104 -0
- package/core/tools/ai/command-code/README.md +62 -0
- package/core/tools/ai/command-code/install.sh +136 -0
- package/core/tools/ai/engram/README.md +44 -0
- package/core/tools/ai/engram/install.sh +127 -0
- package/core/tools/ai/freebuff/README.md +52 -0
- package/core/tools/ai/freebuff/bin/freebuff +28 -0
- package/core/tools/ai/freebuff/helper/freebuff_helper.c +47 -0
- package/core/tools/ai/freebuff/install.sh +301 -0
- package/core/tools/ai/gemini-cli/README.md +46 -0
- package/core/tools/ai/gemini-cli/install.sh +111 -0
- package/core/tools/ai/gentle-ai/README.md +42 -0
- package/core/tools/ai/gentle-ai/install.sh +253 -0
- package/core/tools/ai/gentle-ai/termux-patches.go +256 -0
- package/core/tools/ai/gga/README.md +49 -0
- package/core/tools/ai/gga/install.sh +134 -0
- package/core/tools/ai/hermes-agent/README.md +50 -0
- package/core/tools/ai/hermes-agent/install.sh +80 -0
- package/core/tools/ai/heygen/README.md +24 -0
- package/core/tools/ai/heygen/install.sh +126 -0
- package/core/tools/ai/kilocode-cli/README.md +45 -0
- package/core/tools/ai/kilocode-cli/bin/kilocode +21 -0
- package/core/tools/ai/kilocode-cli/helper/kilocode_helper.c +47 -0
- package/core/tools/ai/kilocode-cli/install.sh +179 -0
- package/core/tools/ai/kimchi-code/README.md +8 -0
- package/core/tools/ai/kimchi-code/install.sh +120 -0
- package/core/tools/ai/kimi-code/README.md +45 -0
- package/core/tools/ai/kimi-code/install.sh +111 -0
- package/core/tools/ai/kiro-cli/README.md +23 -0
- package/core/tools/ai/kiro-cli/install.sh +127 -0
- package/core/tools/ai/mimocode/README.md +43 -0
- package/core/tools/ai/mimocode/bin/mimo +28 -0
- package/core/tools/ai/mimocode/helper/mimocode_helper.c +47 -0
- package/core/tools/ai/mimocode/install.sh +287 -0
- package/core/tools/ai/minimax-cli/README.md +45 -0
- package/core/tools/ai/minimax-cli/install.sh +111 -0
- package/core/tools/ai/mistral-vibe/README.md +46 -0
- package/core/tools/ai/mistral-vibe/install.sh +119 -0
- package/core/tools/ai/odysseus/README.md +35 -0
- package/core/tools/ai/odysseus/install.sh +163 -0
- package/core/tools/ai/ollama/README.md +44 -0
- package/core/tools/ai/ollama/install.sh +78 -0
- package/core/tools/ai/openclaude/README.md +46 -0
- package/core/tools/ai/openclaude/install.sh +110 -0
- package/core/tools/ai/openclaw/README.md +47 -0
- package/core/tools/ai/openclaw/install.sh +112 -0
- package/core/tools/ai/opencode/README.md +51 -0
- package/core/tools/ai/opencode/bin/opencode +28 -0
- package/core/tools/ai/opencode/helper/opencode_helper.c +57 -0
- package/core/tools/ai/opencode/install.sh +290 -0
- package/core/tools/ai/pi/README.md +46 -0
- package/core/tools/ai/pi/install.sh +104 -0
- package/core/tools/ai/qwen-code/README.md +45 -0
- package/core/tools/ai/qwen-code/install.sh +111 -0
- package/core/tools/ai/seedance/README.md +22 -0
- package/core/tools/ai/seedance/install.sh +134 -0
- package/core/tools/ai/veo3/README.md +36 -0
- package/core/tools/ai/veo3/install.sh +151 -0
- package/core/tools/auto/all.sh +57 -0
- package/core/tools/auto/n8n/README.md +47 -0
- package/core/tools/auto/n8n/install.sh +102 -0
- package/core/tools/db/all.sh +60 -0
- package/core/tools/db/mariadb/README.md +43 -0
- package/core/tools/db/mariadb/install.sh +65 -0
- package/core/tools/db/mongodb/README.md +42 -0
- package/core/tools/db/mongodb/install.sh +73 -0
- package/core/tools/db/postgresql/README.md +45 -0
- package/core/tools/db/postgresql/install.sh +65 -0
- package/core/tools/db/sqlite/README.md +44 -0
- package/core/tools/db/sqlite/install.sh +65 -0
- package/core/tools/deploy/all.sh +39 -0
- package/core/tools/deploy/netlify/install.sh +41 -0
- package/core/tools/deploy/railway/install.sh +47 -0
- package/core/tools/deploy/vercel/install.sh +43 -0
- package/core/tools/dev/all.sh +75 -0
- package/core/tools/dev/bat/README.md +44 -0
- package/core/tools/dev/bat/install.sh +82 -0
- package/core/tools/dev/bc/README.md +44 -0
- package/core/tools/dev/bc/install.sh +82 -0
- package/core/tools/dev/cloudflared/README.md +43 -0
- package/core/tools/dev/cloudflared/install.sh +82 -0
- package/core/tools/dev/curl/README.md +43 -0
- package/core/tools/dev/curl/install.sh +82 -0
- package/core/tools/dev/fzf/README.md +44 -0
- package/core/tools/dev/fzf/install.sh +82 -0
- package/core/tools/dev/gh/README.md +43 -0
- package/core/tools/dev/gh/install.sh +82 -0
- package/core/tools/dev/html2text/README.md +44 -0
- package/core/tools/dev/html2text/install.sh +82 -0
- package/core/tools/dev/imagemagick/README.md +44 -0
- package/core/tools/dev/imagemagick/install.sh +82 -0
- package/core/tools/dev/jq/README.md +44 -0
- package/core/tools/dev/jq/install.sh +82 -0
- package/core/tools/dev/lsd/README.md +44 -0
- package/core/tools/dev/lsd/install.sh +82 -0
- package/core/tools/dev/make/README.md +44 -0
- package/core/tools/dev/make/install.sh +82 -0
- package/core/tools/dev/ncurses/README.md +42 -0
- package/core/tools/dev/ncurses/install.sh +82 -0
- package/core/tools/dev/proot/README.md +43 -0
- package/core/tools/dev/proot/install.sh +82 -0
- package/core/tools/dev/shfmt/README.md +44 -0
- package/core/tools/dev/shfmt/install.sh +82 -0
- package/core/tools/dev/tmate/README.md +44 -0
- package/core/tools/dev/tmate/install.sh +82 -0
- package/core/tools/dev/translate/README.md +44 -0
- package/core/tools/dev/translate/install.sh +82 -0
- package/core/tools/dev/tree/README.md +44 -0
- package/core/tools/dev/tree/install.sh +82 -0
- package/core/tools/dev/udocker/README.md +44 -0
- package/core/tools/dev/udocker/install.sh +82 -0
- package/core/tools/dev/wget/README.md +44 -0
- package/core/tools/dev/wget/install.sh +82 -0
- package/core/tools/editor/all.sh +58 -0
- package/core/tools/editor/neovim/README.md +43 -0
- package/core/tools/editor/neovim/install.sh +66 -0
- package/core/tools/editor/nvchad/README.md +45 -0
- package/core/tools/editor/nvchad/install.sh +110 -0
- package/core/tools/lang/all.sh +63 -0
- package/core/tools/lang/clang/README.md +43 -0
- package/core/tools/lang/clang/install.sh +79 -0
- package/core/tools/lang/golang/README.md +43 -0
- package/core/tools/lang/golang/install.sh +79 -0
- package/core/tools/lang/nodejs/README.md +43 -0
- package/core/tools/lang/nodejs/install.sh +79 -0
- package/core/tools/lang/perl/README.md +43 -0
- package/core/tools/lang/perl/install.sh +79 -0
- package/core/tools/lang/php/README.md +43 -0
- package/core/tools/lang/php/install.sh +79 -0
- package/core/tools/lang/python/README.md +43 -0
- package/core/tools/lang/python/install.sh +79 -0
- package/core/tools/lang/rust/README.md +43 -0
- package/core/tools/lang/rust/install.sh +79 -0
- package/core/tools/npm/all.sh +67 -0
- package/core/tools/npm/live-server/README.md +43 -0
- package/core/tools/npm/live-server/install.sh +95 -0
- package/core/tools/npm/localtunnel/README.md +43 -0
- package/core/tools/npm/localtunnel/install.sh +111 -0
- package/core/tools/npm/markserv/README.md +43 -0
- package/core/tools/npm/markserv/install.sh +94 -0
- package/core/tools/npm/ncu/README.md +43 -0
- package/core/tools/npm/ncu/install.sh +94 -0
- package/core/tools/npm/nestjs/README.md +43 -0
- package/core/tools/npm/nestjs/install.sh +94 -0
- package/core/tools/npm/ngrok/README.md +44 -0
- package/core/tools/npm/ngrok/install.sh +94 -0
- package/core/tools/npm/prettier/README.md +43 -0
- package/core/tools/npm/prettier/install.sh +94 -0
- package/core/tools/npm/psqlformat/README.md +43 -0
- package/core/tools/npm/psqlformat/install.sh +94 -0
- package/core/tools/npm/turbopack/README.md +8 -0
- package/core/tools/npm/turbopack/install.sh +41 -0
- package/core/tools/npm/typescript/README.md +43 -0
- package/core/tools/npm/typescript/install.sh +94 -0
- package/core/tools/npm/vercel/README.md +43 -0
- package/core/tools/npm/vercel/install.sh +94 -0
- package/core/tools/shell/all.sh +67 -0
- package/core/tools/shell/better-npm/README.md +43 -0
- package/core/tools/shell/better-npm/install.sh +85 -0
- package/core/tools/shell/fzf-tab/README.md +44 -0
- package/core/tools/shell/fzf-tab/install.sh +85 -0
- package/core/tools/shell/history-substring/README.md +43 -0
- package/core/tools/shell/history-substring/install.sh +85 -0
- package/core/tools/shell/powerlevel10k/README.md +43 -0
- package/core/tools/shell/powerlevel10k/install.sh +85 -0
- package/core/tools/shell/you-should-use/README.md +43 -0
- package/core/tools/shell/you-should-use/install.sh +85 -0
- package/core/tools/shell/zsh-autopair/README.md +44 -0
- package/core/tools/shell/zsh-autopair/install.sh +85 -0
- package/core/tools/shell/zsh-autosuggestions/README.md +43 -0
- package/core/tools/shell/zsh-autosuggestions/install.sh +85 -0
- package/core/tools/shell/zsh-completions/README.md +43 -0
- package/core/tools/shell/zsh-completions/install.sh +85 -0
- package/core/tools/shell/zsh-defer/README.md +43 -0
- package/core/tools/shell/zsh-defer/install.sh +85 -0
- package/core/tools/shell/zsh-syntax-highlighting/README.md +43 -0
- package/core/tools/shell/zsh-syntax-highlighting/install.sh +72 -0
- package/core/tools/ui/all.sh +61 -0
- package/core/tools/ui/banner/README.md +42 -0
- package/core/tools/ui/banner/install.sh +151 -0
- package/core/tools/ui/cursor/README.md +42 -0
- package/core/tools/ui/cursor/install.sh +58 -0
- package/core/tools/ui/extra-keys/README.md +43 -0
- package/core/tools/ui/extra-keys/install.sh +62 -0
- package/core/tools/ui/font/README.md +43 -0
- package/core/tools/ui/font/install.sh +63 -0
- package/core/utils/banner.sh +519 -0
- package/core/utils/bootstrap.sh +25 -0
- package/core/utils/colors.sh +31 -0
- package/core/utils/dialogrc +30 -0
- package/core/utils/env.sh +53 -0
- package/core/utils/log.sh +598 -0
- package/install.sh +272 -0
- 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
|
+
}
|