@adverant/nexus-memory-skill 1.3.0 → 2.1.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/README.md +9 -2
- package/SKILL.md +152 -0
- package/hooks/auto-recall.sh +66 -5
- package/hooks/bead-sync.sh +596 -0
- package/hooks/episode-summary.sh +194 -176
- package/hooks/recall-memory.sh +106 -53
- package/hooks/store-memory.sh +105 -2
- package/package.json +1 -1
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Nexus Memory - Beads Sync Hook
|
|
4
|
+
# Bidirectional sync between local bd (beads) and Nexus GraphRAG
|
|
5
|
+
#
|
|
6
|
+
# Attribution:
|
|
7
|
+
# Beads (bd) is created by Steve Yegge
|
|
8
|
+
# Repository: https://github.com/steveyegge/beads
|
|
9
|
+
# License: See the beads repository for license terms.
|
|
10
|
+
#
|
|
11
|
+
# This hook provides GraphRAG sync capabilities for the beads issue tracker,
|
|
12
|
+
# enabling cross-device sync and semantic search of issues/tasks.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# bead-sync.sh install # Download bd binary if not present
|
|
16
|
+
# bead-sync.sh sync # Full bidirectional sync
|
|
17
|
+
# bead-sync.sh push # Push local beads to GraphRAG
|
|
18
|
+
# bead-sync.sh pull # Pull beads from GraphRAG to local
|
|
19
|
+
# bead-sync.sh sync-latest # Sync only recently changed beads
|
|
20
|
+
# bead-sync.sh query "text" # Query beads from GraphRAG
|
|
21
|
+
#
|
|
22
|
+
# Environment Variables:
|
|
23
|
+
# NEXUS_API_KEY - API key for authentication (REQUIRED)
|
|
24
|
+
# NEXUS_API_URL - API endpoint (default: https://api.adverant.ai)
|
|
25
|
+
# NEXUS_VERBOSE - Set to 1 for debug output
|
|
26
|
+
# BD_VERSION - Beads version to install (default: latest)
|
|
27
|
+
#
|
|
28
|
+
|
|
29
|
+
set -o pipefail
|
|
30
|
+
|
|
31
|
+
# Configuration
|
|
32
|
+
NEXUS_API_KEY="${NEXUS_API_KEY:-}"
|
|
33
|
+
NEXUS_API_URL="${NEXUS_API_URL:-https://api.adverant.ai}"
|
|
34
|
+
VERBOSE="${NEXUS_VERBOSE:-0}"
|
|
35
|
+
BD_VERSION="${BD_VERSION:-latest}"
|
|
36
|
+
|
|
37
|
+
# Paths
|
|
38
|
+
BD_BIN="${HOME}/.local/bin/bd"
|
|
39
|
+
STATE_DIR="${HOME}/.claude/session-env"
|
|
40
|
+
LAST_BEAD_SYNC="${STATE_DIR}/last_bead_sync"
|
|
41
|
+
LAST_BEAD_ID="${STATE_DIR}/last_bead_id"
|
|
42
|
+
|
|
43
|
+
# Logging
|
|
44
|
+
log() {
|
|
45
|
+
if [[ "$VERBOSE" == "1" ]]; then
|
|
46
|
+
echo "[bead-sync] $1" >&2
|
|
47
|
+
fi
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
log_error() {
|
|
51
|
+
echo "[bead-sync] ERROR: $1" >&2
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
log_info() {
|
|
55
|
+
echo "[bead-sync] $1" >&2
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Ensure state directory exists
|
|
59
|
+
mkdir -p "$STATE_DIR"
|
|
60
|
+
mkdir -p "$(dirname "$BD_BIN")"
|
|
61
|
+
|
|
62
|
+
# Check for API key
|
|
63
|
+
check_api_key() {
|
|
64
|
+
if [[ -z "$NEXUS_API_KEY" ]]; then
|
|
65
|
+
log_error "NEXUS_API_KEY environment variable is required but not set."
|
|
66
|
+
log_error "Get your API key from: https://dashboard.adverant.ai/dashboard/api-keys"
|
|
67
|
+
return 1
|
|
68
|
+
fi
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Check dependencies
|
|
73
|
+
check_deps() {
|
|
74
|
+
if ! command -v jq &> /dev/null; then
|
|
75
|
+
log_error "jq is required but not installed. Install with: brew install jq"
|
|
76
|
+
return 1
|
|
77
|
+
fi
|
|
78
|
+
if ! command -v curl &> /dev/null; then
|
|
79
|
+
log_error "curl is required but not installed."
|
|
80
|
+
return 1
|
|
81
|
+
fi
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Detect platform for binary download
|
|
86
|
+
detect_platform() {
|
|
87
|
+
local os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
88
|
+
local arch=$(uname -m)
|
|
89
|
+
|
|
90
|
+
# Normalize architecture
|
|
91
|
+
case "$arch" in
|
|
92
|
+
x86_64) arch="amd64" ;;
|
|
93
|
+
aarch64) arch="arm64" ;;
|
|
94
|
+
arm64) arch="arm64" ;;
|
|
95
|
+
esac
|
|
96
|
+
|
|
97
|
+
echo "${os}_${arch}"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Install bd binary if not present
|
|
101
|
+
install_bd() {
|
|
102
|
+
# Check if already installed and working
|
|
103
|
+
if [[ -f "$BD_BIN" ]] && [[ -x "$BD_BIN" ]]; then
|
|
104
|
+
if "$BD_BIN" --version &>/dev/null; then
|
|
105
|
+
log "bd binary already installed and working at $BD_BIN"
|
|
106
|
+
"$BD_BIN" --version 2>/dev/null || true
|
|
107
|
+
return 0
|
|
108
|
+
else
|
|
109
|
+
log "bd binary exists but not working, reinstalling..."
|
|
110
|
+
rm -f "$BD_BIN"
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
log_info "Installing beads (bd) binary..."
|
|
115
|
+
|
|
116
|
+
local platform=$(detect_platform)
|
|
117
|
+
local download_url
|
|
118
|
+
local temp_dir=$(mktemp -d)
|
|
119
|
+
|
|
120
|
+
if [[ "$BD_VERSION" == "latest" ]]; then
|
|
121
|
+
# Get latest release URL from GitHub API
|
|
122
|
+
# The releases are .tar.gz archives, not raw binaries
|
|
123
|
+
download_url=$(curl -s "https://api.github.com/repos/steveyegge/beads/releases/latest" | \
|
|
124
|
+
jq -r ".assets[] | select(.name | test(\"${platform}\")) | select(.name | endswith(\".tar.gz\")) | .browser_download_url" | head -1)
|
|
125
|
+
else
|
|
126
|
+
download_url="https://github.com/steveyegge/beads/releases/download/${BD_VERSION}/beads_${BD_VERSION}_${platform}.tar.gz"
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
if [[ -z "$download_url" ]] || [[ "$download_url" == "null" ]]; then
|
|
130
|
+
log_error "Could not find bd binary for platform: $platform"
|
|
131
|
+
log_error "Try installing manually: go install github.com/steveyegge/beads/cmd/bd@latest"
|
|
132
|
+
rm -rf "$temp_dir"
|
|
133
|
+
return 1
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
log "Downloading from: $download_url"
|
|
137
|
+
|
|
138
|
+
# Download and extract the tarball
|
|
139
|
+
local tarball="${temp_dir}/beads.tar.gz"
|
|
140
|
+
if curl -sL "$download_url" -o "$tarball"; then
|
|
141
|
+
# Extract to temp directory
|
|
142
|
+
if tar -xzf "$tarball" -C "$temp_dir" 2>/dev/null; then
|
|
143
|
+
# Find the bd binary in the extracted files
|
|
144
|
+
local extracted_bd=$(find "$temp_dir" -name "bd" -type f -perm +111 2>/dev/null | head -1)
|
|
145
|
+
if [[ -z "$extracted_bd" ]]; then
|
|
146
|
+
# Try without execute permission check
|
|
147
|
+
extracted_bd=$(find "$temp_dir" -name "bd" -type f 2>/dev/null | head -1)
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if [[ -n "$extracted_bd" ]] && [[ -f "$extracted_bd" ]]; then
|
|
151
|
+
cp "$extracted_bd" "$BD_BIN"
|
|
152
|
+
chmod +x "$BD_BIN"
|
|
153
|
+
rm -rf "$temp_dir"
|
|
154
|
+
|
|
155
|
+
if "$BD_BIN" --version &>/dev/null; then
|
|
156
|
+
log_info "Successfully installed bd to $BD_BIN"
|
|
157
|
+
"$BD_BIN" --version 2>/dev/null || true
|
|
158
|
+
return 0
|
|
159
|
+
else
|
|
160
|
+
log_error "bd binary installed but not executable"
|
|
161
|
+
rm -f "$BD_BIN"
|
|
162
|
+
return 1
|
|
163
|
+
fi
|
|
164
|
+
else
|
|
165
|
+
log_error "Could not find bd binary in archive"
|
|
166
|
+
log "Archive contents:"
|
|
167
|
+
tar -tzf "$tarball" 2>/dev/null | head -10 >&2
|
|
168
|
+
rm -rf "$temp_dir"
|
|
169
|
+
return 1
|
|
170
|
+
fi
|
|
171
|
+
else
|
|
172
|
+
log_error "Failed to extract tarball"
|
|
173
|
+
rm -rf "$temp_dir"
|
|
174
|
+
return 1
|
|
175
|
+
fi
|
|
176
|
+
else
|
|
177
|
+
log_error "Failed to download bd binary"
|
|
178
|
+
rm -rf "$temp_dir"
|
|
179
|
+
return 1
|
|
180
|
+
fi
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Check if bd is available
|
|
184
|
+
ensure_bd() {
|
|
185
|
+
if [[ ! -x "$BD_BIN" ]] && ! command -v bd &> /dev/null; then
|
|
186
|
+
install_bd || return 1
|
|
187
|
+
fi
|
|
188
|
+
return 0
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Get the bd command (prefer local install)
|
|
192
|
+
get_bd_cmd() {
|
|
193
|
+
if [[ -x "$BD_BIN" ]]; then
|
|
194
|
+
echo "$BD_BIN"
|
|
195
|
+
elif command -v bd &> /dev/null; then
|
|
196
|
+
echo "bd"
|
|
197
|
+
else
|
|
198
|
+
echo ""
|
|
199
|
+
fi
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Get current project info
|
|
203
|
+
get_project_info() {
|
|
204
|
+
local project_dir=$(pwd)
|
|
205
|
+
local project_name=$(basename "$project_dir")
|
|
206
|
+
local git_remote=$(git remote get-url origin 2>/dev/null || echo "local")
|
|
207
|
+
|
|
208
|
+
echo "{\"dir\": \"$project_dir\", \"name\": \"$project_name\", \"remote\": \"$git_remote\"}"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# Push a single bead to GraphRAG
|
|
212
|
+
push_bead_to_graphrag() {
|
|
213
|
+
local bead_json="$1"
|
|
214
|
+
local project_info="$2"
|
|
215
|
+
|
|
216
|
+
local bead_id=$(echo "$bead_json" | jq -r '.id // .ID // empty')
|
|
217
|
+
local title=$(echo "$bead_json" | jq -r '.title // .Title // empty')
|
|
218
|
+
local description=$(echo "$bead_json" | jq -r '.description // .Description // ""')
|
|
219
|
+
local status=$(echo "$bead_json" | jq -r '.status // .Status // "open"')
|
|
220
|
+
local priority=$(echo "$bead_json" | jq -r '.priority // .Priority // "P2"')
|
|
221
|
+
local issue_type=$(echo "$bead_json" | jq -r '.issueType // .type // "task"')
|
|
222
|
+
local created_at=$(echo "$bead_json" | jq -r '.createdAt // .created_at // empty')
|
|
223
|
+
local deps=$(echo "$bead_json" | jq -c '.dependencies // []')
|
|
224
|
+
|
|
225
|
+
local project_name=$(echo "$project_info" | jq -r '.name')
|
|
226
|
+
local project_dir=$(echo "$project_info" | jq -r '.dir')
|
|
227
|
+
|
|
228
|
+
if [[ -z "$bead_id" ]]; then
|
|
229
|
+
log "Skipping bead with no ID"
|
|
230
|
+
return 0
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
log "Pushing bead $bead_id: $title"
|
|
234
|
+
|
|
235
|
+
# Build content for memory storage
|
|
236
|
+
local content="[Bead $bead_id] $title
|
|
237
|
+
|
|
238
|
+
Status: $status
|
|
239
|
+
Priority: $priority
|
|
240
|
+
Type: $issue_type
|
|
241
|
+
Project: $project_name
|
|
242
|
+
|
|
243
|
+
$description"
|
|
244
|
+
|
|
245
|
+
# Build tags array
|
|
246
|
+
local tags=$(jq -n \
|
|
247
|
+
--arg id "$bead_id" \
|
|
248
|
+
--arg status "$status" \
|
|
249
|
+
--arg priority "$priority" \
|
|
250
|
+
--arg project "$project_name" \
|
|
251
|
+
--arg type "$issue_type" \
|
|
252
|
+
'["bead", "type:bead", ("bead-id:" + $id), ("status:" + $status), ("priority:" + $priority), ("project:" + $project), ("issue-type:" + $type)]')
|
|
253
|
+
|
|
254
|
+
# Build metadata
|
|
255
|
+
local metadata=$(jq -n \
|
|
256
|
+
--arg bead_id "$bead_id" \
|
|
257
|
+
--arg title "$title" \
|
|
258
|
+
--arg status "$status" \
|
|
259
|
+
--arg priority "$priority" \
|
|
260
|
+
--arg issue_type "$issue_type" \
|
|
261
|
+
--arg project "$project_name" \
|
|
262
|
+
--arg project_dir "$project_dir" \
|
|
263
|
+
--arg created_at "$created_at" \
|
|
264
|
+
--argjson deps "$deps" \
|
|
265
|
+
'{
|
|
266
|
+
bead_id: $bead_id,
|
|
267
|
+
title: $title,
|
|
268
|
+
status: $status,
|
|
269
|
+
priority: $priority,
|
|
270
|
+
issue_type: $issue_type,
|
|
271
|
+
project: $project,
|
|
272
|
+
project_dir: $project_dir,
|
|
273
|
+
created_at: $created_at,
|
|
274
|
+
dependencies: $deps
|
|
275
|
+
}')
|
|
276
|
+
|
|
277
|
+
# Build full payload
|
|
278
|
+
local payload=$(jq -n \
|
|
279
|
+
--arg content "$content" \
|
|
280
|
+
--argjson tags "$tags" \
|
|
281
|
+
--argjson metadata "$metadata" \
|
|
282
|
+
--arg bead_id "$bead_id" \
|
|
283
|
+
'{
|
|
284
|
+
content: $content,
|
|
285
|
+
event_type: "bead",
|
|
286
|
+
tags: $tags,
|
|
287
|
+
metadata: $metadata,
|
|
288
|
+
extract_entities: true,
|
|
289
|
+
entity_types: ["bead", "task", "feature", "bug", "project", "file", "function"],
|
|
290
|
+
domain: "code",
|
|
291
|
+
create_relationships: true,
|
|
292
|
+
episodic: {
|
|
293
|
+
type: "bead",
|
|
294
|
+
interaction_id: $bead_id
|
|
295
|
+
}
|
|
296
|
+
}')
|
|
297
|
+
|
|
298
|
+
# Send to GraphRAG
|
|
299
|
+
local response=$(curl -s -X POST "${NEXUS_API_URL}/api/memory/store" \
|
|
300
|
+
-H "Authorization: Bearer ${NEXUS_API_KEY}" \
|
|
301
|
+
-H "Content-Type: application/json" \
|
|
302
|
+
-d "$payload" \
|
|
303
|
+
--max-time 10)
|
|
304
|
+
|
|
305
|
+
local success=$(echo "$response" | jq -r '.success // false')
|
|
306
|
+
|
|
307
|
+
if [[ "$success" == "true" ]]; then
|
|
308
|
+
log "Successfully pushed bead $bead_id"
|
|
309
|
+
echo "$bead_id" > "$LAST_BEAD_ID"
|
|
310
|
+
return 0
|
|
311
|
+
else
|
|
312
|
+
log_error "Failed to push bead $bead_id: $(echo "$response" | jq -r '.message // .error // "unknown error"')"
|
|
313
|
+
return 1
|
|
314
|
+
fi
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# Push all local beads to GraphRAG
|
|
318
|
+
push_beads() {
|
|
319
|
+
check_api_key || return 1
|
|
320
|
+
ensure_bd || return 1
|
|
321
|
+
|
|
322
|
+
local bd_cmd=$(get_bd_cmd)
|
|
323
|
+
if [[ -z "$bd_cmd" ]]; then
|
|
324
|
+
log_error "bd command not available"
|
|
325
|
+
return 1
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# Check if beads is initialized in this repo
|
|
329
|
+
if [[ ! -d ".beads" ]] && ! "$bd_cmd" list &>/dev/null; then
|
|
330
|
+
log "No beads found in this repository. Run 'bd init' to initialize."
|
|
331
|
+
return 0
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
local project_info=$(get_project_info)
|
|
335
|
+
local beads=$("$bd_cmd" list --json 2>/dev/null || echo "[]")
|
|
336
|
+
local count=$(echo "$beads" | jq 'length')
|
|
337
|
+
|
|
338
|
+
log_info "Pushing $count beads to GraphRAG..."
|
|
339
|
+
|
|
340
|
+
local pushed=0
|
|
341
|
+
local failed=0
|
|
342
|
+
|
|
343
|
+
echo "$beads" | jq -c '.[]' 2>/dev/null | while read -r bead; do
|
|
344
|
+
if push_bead_to_graphrag "$bead" "$project_info"; then
|
|
345
|
+
((pushed++)) || true
|
|
346
|
+
else
|
|
347
|
+
((failed++)) || true
|
|
348
|
+
fi
|
|
349
|
+
done
|
|
350
|
+
|
|
351
|
+
# Record sync timestamp
|
|
352
|
+
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$LAST_BEAD_SYNC"
|
|
353
|
+
|
|
354
|
+
log_info "Push complete. Pushed: $pushed, Failed: $failed"
|
|
355
|
+
return 0
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
# Pull beads from GraphRAG and create locally if missing
|
|
359
|
+
pull_beads() {
|
|
360
|
+
check_api_key || return 1
|
|
361
|
+
ensure_bd || return 1
|
|
362
|
+
|
|
363
|
+
local bd_cmd=$(get_bd_cmd)
|
|
364
|
+
if [[ -z "$bd_cmd" ]]; then
|
|
365
|
+
log_error "bd command not available"
|
|
366
|
+
return 1
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
local project_info=$(get_project_info)
|
|
370
|
+
local project_name=$(echo "$project_info" | jq -r '.name')
|
|
371
|
+
|
|
372
|
+
log_info "Pulling beads from GraphRAG for project: $project_name"
|
|
373
|
+
|
|
374
|
+
# Query GraphRAG for beads from this project
|
|
375
|
+
local query_payload=$(jq -n \
|
|
376
|
+
--arg project "$project_name" \
|
|
377
|
+
'{
|
|
378
|
+
query: "beads tasks issues",
|
|
379
|
+
filters: {
|
|
380
|
+
tags: ["type:bead", ("project:" + $project)]
|
|
381
|
+
},
|
|
382
|
+
limit: 100,
|
|
383
|
+
includeEpisodic: true
|
|
384
|
+
}')
|
|
385
|
+
|
|
386
|
+
local response=$(curl -s -X POST "${NEXUS_API_URL}/api/retrieve/enhanced" \
|
|
387
|
+
-H "Authorization: Bearer ${NEXUS_API_KEY}" \
|
|
388
|
+
-H "Content-Type: application/json" \
|
|
389
|
+
-d "$query_payload" \
|
|
390
|
+
--max-time 15)
|
|
391
|
+
|
|
392
|
+
local memories=$(echo "$response" | jq -c '.data.memories // .memories // []')
|
|
393
|
+
local count=$(echo "$memories" | jq 'length')
|
|
394
|
+
|
|
395
|
+
log "Found $count beads in GraphRAG"
|
|
396
|
+
|
|
397
|
+
if [[ "$count" == "0" ]]; then
|
|
398
|
+
log "No beads to pull from GraphRAG"
|
|
399
|
+
return 0
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Check if beads is initialized
|
|
403
|
+
if [[ ! -d ".beads" ]]; then
|
|
404
|
+
log "Initializing beads in this repository..."
|
|
405
|
+
"$bd_cmd" init 2>/dev/null || true
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
local created=0
|
|
409
|
+
local skipped=0
|
|
410
|
+
|
|
411
|
+
# For each bead in GraphRAG, check if it exists locally
|
|
412
|
+
echo "$memories" | jq -c '.[]' 2>/dev/null | while read -r memory; do
|
|
413
|
+
local bead_id=$(echo "$memory" | jq -r '.metadata.bead_id // empty')
|
|
414
|
+
local title=$(echo "$memory" | jq -r '.metadata.title // empty')
|
|
415
|
+
local status=$(echo "$memory" | jq -r '.metadata.status // "open"')
|
|
416
|
+
local priority=$(echo "$memory" | jq -r '.metadata.priority // "P2"')
|
|
417
|
+
|
|
418
|
+
if [[ -z "$bead_id" ]]; then
|
|
419
|
+
continue
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
# Check if bead exists locally
|
|
423
|
+
if "$bd_cmd" show "$bead_id" &>/dev/null; then
|
|
424
|
+
log "Bead $bead_id already exists locally, skipping"
|
|
425
|
+
((skipped++)) || true
|
|
426
|
+
continue
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# Create bead locally
|
|
430
|
+
log "Creating bead $bead_id: $title"
|
|
431
|
+
|
|
432
|
+
# Note: bd create may not support --id flag, this is a best-effort attempt
|
|
433
|
+
# The actual bd CLI might have different flags
|
|
434
|
+
if "$bd_cmd" create "$title" --priority "$priority" 2>/dev/null; then
|
|
435
|
+
((created++)) || true
|
|
436
|
+
else
|
|
437
|
+
log "Could not create bead locally (may already exist with different ID)"
|
|
438
|
+
fi
|
|
439
|
+
done
|
|
440
|
+
|
|
441
|
+
log_info "Pull complete. Created: $created, Skipped: $skipped"
|
|
442
|
+
return 0
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
# Full bidirectional sync
|
|
446
|
+
sync_bidirectional() {
|
|
447
|
+
log_info "Starting bidirectional sync..."
|
|
448
|
+
|
|
449
|
+
push_beads
|
|
450
|
+
pull_beads
|
|
451
|
+
|
|
452
|
+
log_info "Bidirectional sync complete"
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
# Sync only recently changed beads (since last sync)
|
|
456
|
+
sync_latest() {
|
|
457
|
+
check_api_key || return 1
|
|
458
|
+
ensure_bd || return 1
|
|
459
|
+
|
|
460
|
+
local bd_cmd=$(get_bd_cmd)
|
|
461
|
+
if [[ -z "$bd_cmd" ]]; then
|
|
462
|
+
log_error "bd command not available"
|
|
463
|
+
return 1
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
local last_sync=""
|
|
467
|
+
if [[ -f "$LAST_BEAD_SYNC" ]]; then
|
|
468
|
+
last_sync=$(cat "$LAST_BEAD_SYNC")
|
|
469
|
+
fi
|
|
470
|
+
|
|
471
|
+
log "Syncing beads changed since: ${last_sync:-never}"
|
|
472
|
+
|
|
473
|
+
# For now, just do a full push (bd doesn't have --since flag)
|
|
474
|
+
# In the future, we could track which beads have changed
|
|
475
|
+
push_beads
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
# Query beads from GraphRAG
|
|
479
|
+
query_beads() {
|
|
480
|
+
local query_text="$1"
|
|
481
|
+
|
|
482
|
+
check_api_key || return 1
|
|
483
|
+
|
|
484
|
+
if [[ -z "$query_text" ]]; then
|
|
485
|
+
query_text="beads tasks issues"
|
|
486
|
+
fi
|
|
487
|
+
|
|
488
|
+
local project_info=$(get_project_info)
|
|
489
|
+
local project_name=$(echo "$project_info" | jq -r '.name')
|
|
490
|
+
|
|
491
|
+
log "Querying GraphRAG for: $query_text"
|
|
492
|
+
|
|
493
|
+
local query_payload=$(jq -n \
|
|
494
|
+
--arg query "$query_text" \
|
|
495
|
+
--arg project "$project_name" \
|
|
496
|
+
'{
|
|
497
|
+
query: $query,
|
|
498
|
+
filters: {
|
|
499
|
+
tags: ["type:bead"]
|
|
500
|
+
},
|
|
501
|
+
limit: 20,
|
|
502
|
+
includeEpisodic: true
|
|
503
|
+
}')
|
|
504
|
+
|
|
505
|
+
local response=$(curl -s -X POST "${NEXUS_API_URL}/api/retrieve/enhanced" \
|
|
506
|
+
-H "Authorization: Bearer ${NEXUS_API_KEY}" \
|
|
507
|
+
-H "Content-Type: application/json" \
|
|
508
|
+
-d "$query_payload" \
|
|
509
|
+
--max-time 15)
|
|
510
|
+
|
|
511
|
+
# Pretty print results
|
|
512
|
+
echo "$response" | jq -r '.data.memories // .memories // [] | .[] |
|
|
513
|
+
"[\(.metadata.bead_id // "?")] \(.metadata.title // .content[0:50]) (\(.metadata.status // "?"))"'
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
# Show help
|
|
517
|
+
show_help() {
|
|
518
|
+
cat << 'EOF'
|
|
519
|
+
Beads Sync - Bidirectional sync between local bd and Nexus GraphRAG
|
|
520
|
+
|
|
521
|
+
Attribution:
|
|
522
|
+
Beads (bd) is created by Steve Yegge
|
|
523
|
+
https://github.com/steveyegge/beads
|
|
524
|
+
|
|
525
|
+
Usage:
|
|
526
|
+
bead-sync.sh <command> [args]
|
|
527
|
+
|
|
528
|
+
Commands:
|
|
529
|
+
install Download and install bd binary if not present
|
|
530
|
+
sync Full bidirectional sync (push local, pull remote)
|
|
531
|
+
push Push all local beads to GraphRAG
|
|
532
|
+
pull Pull beads from GraphRAG to local repository
|
|
533
|
+
sync-latest Sync only recently changed beads
|
|
534
|
+
query "text" Search for beads in GraphRAG
|
|
535
|
+
help Show this help message
|
|
536
|
+
|
|
537
|
+
Environment Variables:
|
|
538
|
+
NEXUS_API_KEY API key for authentication (required)
|
|
539
|
+
NEXUS_API_URL API endpoint (default: https://api.adverant.ai)
|
|
540
|
+
NEXUS_VERBOSE Set to 1 for debug output
|
|
541
|
+
BD_VERSION Beads version to install (default: latest)
|
|
542
|
+
|
|
543
|
+
Examples:
|
|
544
|
+
# Install bd binary
|
|
545
|
+
bead-sync.sh install
|
|
546
|
+
|
|
547
|
+
# Full sync
|
|
548
|
+
bead-sync.sh sync
|
|
549
|
+
|
|
550
|
+
# Push local beads to GraphRAG
|
|
551
|
+
bead-sync.sh push
|
|
552
|
+
|
|
553
|
+
# Search for authentication-related beads
|
|
554
|
+
bead-sync.sh query "authentication login"
|
|
555
|
+
EOF
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
# Main command dispatcher
|
|
559
|
+
main() {
|
|
560
|
+
local command="${1:-help}"
|
|
561
|
+
shift || true
|
|
562
|
+
|
|
563
|
+
check_deps || exit 1
|
|
564
|
+
|
|
565
|
+
case "$command" in
|
|
566
|
+
install)
|
|
567
|
+
install_bd
|
|
568
|
+
;;
|
|
569
|
+
sync)
|
|
570
|
+
sync_bidirectional
|
|
571
|
+
;;
|
|
572
|
+
push)
|
|
573
|
+
push_beads
|
|
574
|
+
;;
|
|
575
|
+
pull)
|
|
576
|
+
pull_beads
|
|
577
|
+
;;
|
|
578
|
+
sync-latest)
|
|
579
|
+
sync_latest
|
|
580
|
+
;;
|
|
581
|
+
query)
|
|
582
|
+
query_beads "$1"
|
|
583
|
+
;;
|
|
584
|
+
help|--help|-h)
|
|
585
|
+
show_help
|
|
586
|
+
;;
|
|
587
|
+
*)
|
|
588
|
+
log_error "Unknown command: $command"
|
|
589
|
+
show_help
|
|
590
|
+
exit 1
|
|
591
|
+
;;
|
|
592
|
+
esac
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
# Run main
|
|
596
|
+
main "$@"
|