aidevops 2.172.18 → 2.172.19
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/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +2 -1
- package/scripts/npm-postinstall.cjs +13 -2
- package/setup-modules/agent-deploy.sh +627 -0
- package/setup-modules/config.sh +183 -0
- package/setup-modules/core.sh +572 -0
- package/setup-modules/mcp-setup.sh +766 -0
- package/setup-modules/migrations.sh +961 -0
- package/setup-modules/plugins.sh +588 -0
- package/setup-modules/shell-env.sh +892 -0
- package/setup-modules/tool-install.sh +1373 -0
- package/setup.sh +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.172.
|
|
1
|
+
2.172.19
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aidevops",
|
|
3
|
-
"version": "2.172.
|
|
3
|
+
"version": "2.172.19",
|
|
4
4
|
"description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"bin/",
|
|
59
59
|
"aidevops.sh",
|
|
60
60
|
"setup.sh",
|
|
61
|
+
"setup-modules/",
|
|
61
62
|
"VERSION",
|
|
62
63
|
"scripts/npm-postinstall.cjs",
|
|
63
64
|
"templates/"
|
|
@@ -26,7 +26,14 @@ try {
|
|
|
26
26
|
const log = (msg = '') => {
|
|
27
27
|
const line = msg + '\n';
|
|
28
28
|
if (ttyFd !== null) {
|
|
29
|
-
|
|
29
|
+
try {
|
|
30
|
+
fs.writeSync(ttyFd, line);
|
|
31
|
+
} catch {
|
|
32
|
+
// TTY write failed. Fall back to stderr for this and subsequent calls.
|
|
33
|
+
try { fs.closeSync(ttyFd); } catch { /* best effort to close */ }
|
|
34
|
+
ttyFd = null;
|
|
35
|
+
process.stderr.write(line);
|
|
36
|
+
}
|
|
30
37
|
} else {
|
|
31
38
|
process.stderr.write(line);
|
|
32
39
|
}
|
|
@@ -72,5 +79,9 @@ log('');
|
|
|
72
79
|
|
|
73
80
|
// Clean up
|
|
74
81
|
if (ttyFd !== null) {
|
|
75
|
-
|
|
82
|
+
try {
|
|
83
|
+
fs.closeSync(ttyFd);
|
|
84
|
+
} catch {
|
|
85
|
+
// Ignore errors on close, as there's nothing more to do.
|
|
86
|
+
}
|
|
76
87
|
}
|
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Agent deployment functions: deploy_aidevops_agents, deploy_ai_templates, inject_agents_reference, safety-hooks, beads
|
|
3
|
+
# Part of aidevops setup.sh modularization (t316.3)
|
|
4
|
+
|
|
5
|
+
# Shell safety baseline
|
|
6
|
+
set -Eeuo pipefail
|
|
7
|
+
IFS=$'\n\t'
|
|
8
|
+
# shellcheck disable=SC2154 # rc is assigned by $? in the trap string
|
|
9
|
+
trap 'rc=$?; echo "[ERROR] ${BASH_SOURCE[0]}:${LINENO} exit $rc" >&2' ERR
|
|
10
|
+
shopt -s inherit_errexit 2>/dev/null || true
|
|
11
|
+
|
|
12
|
+
deploy_ai_templates() {
|
|
13
|
+
print_info "Deploying AI assistant templates..."
|
|
14
|
+
|
|
15
|
+
if [[ -f "templates/deploy-templates.sh" ]]; then
|
|
16
|
+
print_info "Running template deployment script..."
|
|
17
|
+
if bash templates/deploy-templates.sh; then
|
|
18
|
+
print_success "AI assistant templates deployed successfully"
|
|
19
|
+
else
|
|
20
|
+
print_warning "Template deployment encountered issues (non-critical)"
|
|
21
|
+
fi
|
|
22
|
+
else
|
|
23
|
+
print_warning "Template deployment script not found - skipping"
|
|
24
|
+
fi
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
extract_opencode_prompts() {
|
|
29
|
+
local extract_script=".agents/scripts/extract-opencode-prompts.sh"
|
|
30
|
+
if [[ -f "$extract_script" ]]; then
|
|
31
|
+
if bash "$extract_script"; then
|
|
32
|
+
print_success "OpenCode prompts extracted"
|
|
33
|
+
else
|
|
34
|
+
print_warning "OpenCode prompt extraction encountered issues (non-critical)"
|
|
35
|
+
fi
|
|
36
|
+
fi
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
check_opencode_prompt_drift() {
|
|
41
|
+
local drift_script=".agents/scripts/opencode-prompt-drift-check.sh"
|
|
42
|
+
if [[ -f "$drift_script" ]]; then
|
|
43
|
+
local output exit_code=0
|
|
44
|
+
output=$(bash "$drift_script" --quiet 2>/dev/null) || exit_code=$?
|
|
45
|
+
if [[ "$exit_code" -eq 1 && "$output" == PROMPT_DRIFT* ]]; then
|
|
46
|
+
local local_hash upstream_hash
|
|
47
|
+
local_hash=$(echo "$output" | cut -d'|' -f2)
|
|
48
|
+
upstream_hash=$(echo "$output" | cut -d'|' -f3)
|
|
49
|
+
print_warning "OpenCode upstream prompt has changed (${local_hash} → ${upstream_hash})"
|
|
50
|
+
print_info " Review: https://github.com/anomalyco/opencode/compare/${local_hash}...${upstream_hash}"
|
|
51
|
+
print_info " Update .agents/prompts/build.txt if needed"
|
|
52
|
+
elif [[ "$exit_code" -eq 0 ]]; then
|
|
53
|
+
print_success "OpenCode prompt in sync with upstream"
|
|
54
|
+
else
|
|
55
|
+
print_warning "Could not check prompt drift (network issue or missing dependency)"
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
return 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
deploy_aidevops_agents() {
|
|
62
|
+
print_info "Deploying aidevops agents to ~/.aidevops/agents/..."
|
|
63
|
+
|
|
64
|
+
# Use INSTALL_DIR (set by setup.sh) — BASH_SOURCE[0] points to setup-modules/
|
|
65
|
+
# which is not the repo root, so we can't derive .agents/ from it
|
|
66
|
+
local repo_dir="${INSTALL_DIR:?INSTALL_DIR must be set by setup.sh}"
|
|
67
|
+
local source_dir="$repo_dir/.agents"
|
|
68
|
+
local target_dir="$HOME/.aidevops/agents"
|
|
69
|
+
local plugins_file="$HOME/.config/aidevops/plugins.json"
|
|
70
|
+
|
|
71
|
+
# Validate source directory exists (catches curl install from wrong directory)
|
|
72
|
+
if [[ ! -d "$source_dir" ]]; then
|
|
73
|
+
print_error "Agent source directory not found: $source_dir"
|
|
74
|
+
print_info "This usually means setup.sh was run from the wrong directory."
|
|
75
|
+
print_info "The bootstrap should have cloned the repo and re-executed."
|
|
76
|
+
print_info ""
|
|
77
|
+
print_info "To fix manually:"
|
|
78
|
+
print_info " cd ~/Git/aidevops && ./setup.sh"
|
|
79
|
+
return 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Collect plugin namespace directories to preserve during deployment
|
|
83
|
+
local -a plugin_namespaces=()
|
|
84
|
+
if [[ -f "$plugins_file" ]] && command -v jq &>/dev/null; then
|
|
85
|
+
local ns
|
|
86
|
+
local safe_ns
|
|
87
|
+
while IFS= read -r ns; do
|
|
88
|
+
if [[ -n "$ns" ]] && safe_ns=$(sanitize_plugin_namespace "$ns" 2>/dev/null); then
|
|
89
|
+
plugin_namespaces+=("$safe_ns")
|
|
90
|
+
fi
|
|
91
|
+
done < <(jq -r '.plugins[].namespace // empty' "$plugins_file" 2>/dev/null)
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Create backup if target exists (with rotation)
|
|
95
|
+
if [[ -d "$target_dir" ]]; then
|
|
96
|
+
create_backup_with_rotation "$target_dir" "agents"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Create target directory and copy agents
|
|
100
|
+
mkdir -p "$target_dir"
|
|
101
|
+
|
|
102
|
+
# If clean mode, remove stale files first (preserving user and plugin directories)
|
|
103
|
+
if [[ "$CLEAN_MODE" == "true" ]]; then
|
|
104
|
+
# Build list of directories to preserve: custom, draft, plus plugin namespaces
|
|
105
|
+
local -a preserved_dirs=("custom" "draft")
|
|
106
|
+
if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then
|
|
107
|
+
for pns in "${plugin_namespaces[@]}"; do
|
|
108
|
+
preserved_dirs+=("$pns")
|
|
109
|
+
done
|
|
110
|
+
fi
|
|
111
|
+
print_info "Clean mode: removing stale files from $target_dir (preserving ${preserved_dirs[*]})"
|
|
112
|
+
local tmp_preserve
|
|
113
|
+
tmp_preserve="$(mktemp -d)"
|
|
114
|
+
trap 'rm -rf "${tmp_preserve:-}"' RETURN
|
|
115
|
+
if [[ -z "$tmp_preserve" || ! -d "$tmp_preserve" ]]; then
|
|
116
|
+
print_error "Failed to create temp dir for preserving agents"
|
|
117
|
+
return 1
|
|
118
|
+
fi
|
|
119
|
+
local preserve_failed=false
|
|
120
|
+
for pdir in "${preserved_dirs[@]}"; do
|
|
121
|
+
if [[ -d "$target_dir/$pdir" ]]; then
|
|
122
|
+
if ! cp -R "$target_dir/$pdir" "$tmp_preserve/$pdir"; then
|
|
123
|
+
preserve_failed=true
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
done
|
|
127
|
+
if [[ "$preserve_failed" == "true" ]]; then
|
|
128
|
+
print_error "Failed to preserve user/plugin agents; aborting clean"
|
|
129
|
+
rm -rf "$tmp_preserve"
|
|
130
|
+
return 1
|
|
131
|
+
fi
|
|
132
|
+
rm -rf "${target_dir:?}"/*
|
|
133
|
+
# Restore preserved directories
|
|
134
|
+
for pdir in "${preserved_dirs[@]}"; do
|
|
135
|
+
if [[ -d "$tmp_preserve/$pdir" ]]; then
|
|
136
|
+
cp -R "$tmp_preserve/$pdir" "$target_dir/$pdir"
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
rm -rf "$tmp_preserve"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Copy all agent files and folders, excluding:
|
|
143
|
+
# - loop-state/ (local runtime state, not agents)
|
|
144
|
+
# - custom/ (user's private agents, never overwritten)
|
|
145
|
+
# - draft/ (user's experimental agents, never overwritten)
|
|
146
|
+
# - plugin namespace directories (managed separately)
|
|
147
|
+
# Use rsync for selective exclusion
|
|
148
|
+
local deploy_ok=false
|
|
149
|
+
if command -v rsync &>/dev/null; then
|
|
150
|
+
local -a rsync_excludes=("--exclude=loop-state/" "--exclude=custom/" "--exclude=draft/")
|
|
151
|
+
if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then
|
|
152
|
+
for pns in "${plugin_namespaces[@]}"; do
|
|
153
|
+
rsync_excludes+=("--exclude=${pns}/")
|
|
154
|
+
done
|
|
155
|
+
fi
|
|
156
|
+
if rsync -a "${rsync_excludes[@]}" "$source_dir/" "$target_dir/"; then
|
|
157
|
+
deploy_ok=true
|
|
158
|
+
fi
|
|
159
|
+
else
|
|
160
|
+
# Fallback: use tar with exclusions to match rsync behavior
|
|
161
|
+
local -a tar_excludes=("--exclude=loop-state" "--exclude=custom" "--exclude=draft")
|
|
162
|
+
if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then
|
|
163
|
+
for pns in "${plugin_namespaces[@]}"; do
|
|
164
|
+
tar_excludes+=("--exclude=$pns")
|
|
165
|
+
done
|
|
166
|
+
fi
|
|
167
|
+
if (cd "$source_dir" && tar cf - "${tar_excludes[@]}" .) | (cd "$target_dir" && tar xf -); then
|
|
168
|
+
deploy_ok=true
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
if [[ "$deploy_ok" == "true" ]]; then
|
|
173
|
+
print_success "Deployed agents to $target_dir"
|
|
174
|
+
|
|
175
|
+
# Set permissions on scripts (top-level and modularised subdirectories)
|
|
176
|
+
chmod +x "$target_dir/scripts/"*.sh 2>/dev/null || true
|
|
177
|
+
find "$target_dir/scripts" -mindepth 2 -name "*.sh" -exec chmod +x {} + 2>/dev/null || true
|
|
178
|
+
|
|
179
|
+
# Count what was deployed
|
|
180
|
+
local agent_count
|
|
181
|
+
agent_count=$(find "$target_dir" -name "*.md" -type f | wc -l | tr -d ' ')
|
|
182
|
+
local script_count
|
|
183
|
+
script_count=$(find "$target_dir/scripts" -name "*.sh" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
184
|
+
|
|
185
|
+
print_info "Deployed $agent_count agent files and $script_count scripts"
|
|
186
|
+
|
|
187
|
+
# Copy VERSION file from repo root to deployed agents
|
|
188
|
+
if [[ -f "$repo_dir/VERSION" ]]; then
|
|
189
|
+
if cp "$repo_dir/VERSION" "$target_dir/VERSION"; then
|
|
190
|
+
print_info "Copied VERSION file to deployed agents"
|
|
191
|
+
else
|
|
192
|
+
print_warning "Failed to copy VERSION file (Plan+ may not read version correctly)"
|
|
193
|
+
fi
|
|
194
|
+
else
|
|
195
|
+
print_warning "VERSION file not found in repo root"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Inject extracted OpenCode plan-reminder into Plan+ if available
|
|
199
|
+
local plan_reminder="$HOME/.aidevops/cache/opencode-prompts/plan-reminder.txt"
|
|
200
|
+
local plan_plus="$target_dir/plan-plus.md"
|
|
201
|
+
if [[ -f "$plan_reminder" && -f "$plan_plus" ]]; then
|
|
202
|
+
# Check if plan-plus.md has the placeholder marker
|
|
203
|
+
if grep -q "OPENCODE-PLAN-REMINDER-INJECT" "$plan_plus"; then
|
|
204
|
+
# Replace placeholder with extracted content using sed
|
|
205
|
+
# (awk -v doesn't handle multi-line content with special chars well)
|
|
206
|
+
local tmp_file
|
|
207
|
+
tmp_file=$(mktemp)
|
|
208
|
+
trap 'rm -f "${tmp_file:-}"' RETURN
|
|
209
|
+
local in_placeholder=false
|
|
210
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
211
|
+
if [[ "$line" == *"OPENCODE-PLAN-REMINDER-INJECT-START"* ]]; then
|
|
212
|
+
echo "$line" >>"$tmp_file"
|
|
213
|
+
cat "$plan_reminder" >>"$tmp_file"
|
|
214
|
+
in_placeholder=true
|
|
215
|
+
elif [[ "$line" == *"OPENCODE-PLAN-REMINDER-INJECT-END"* ]]; then
|
|
216
|
+
echo "$line" >>"$tmp_file"
|
|
217
|
+
in_placeholder=false
|
|
218
|
+
elif [[ "$in_placeholder" == false ]]; then
|
|
219
|
+
echo "$line" >>"$tmp_file"
|
|
220
|
+
fi
|
|
221
|
+
done <"$plan_plus"
|
|
222
|
+
mv "$tmp_file" "$plan_plus"
|
|
223
|
+
print_info "Injected OpenCode plan-reminder into Plan+"
|
|
224
|
+
fi
|
|
225
|
+
fi
|
|
226
|
+
# Migrate mailbox from TOON files to SQLite (if old files exist)
|
|
227
|
+
local aidevops_workspace_dir="${AIDEVOPS_WORKSPACE_DIR:-$HOME/.aidevops/.agent-workspace}"
|
|
228
|
+
local mail_dir="${AIDEVOPS_MAIL_DIR:-${aidevops_workspace_dir}/mail}"
|
|
229
|
+
local mail_script="$target_dir/scripts/mail-helper.sh"
|
|
230
|
+
if [[ -x "$mail_script" ]] && find "$mail_dir" -name "*.toon" 2>/dev/null | grep -q .; then
|
|
231
|
+
if "$mail_script" migrate; then
|
|
232
|
+
print_success "Mailbox migration complete"
|
|
233
|
+
else
|
|
234
|
+
print_warning "Mailbox migration had issues (non-critical, old files preserved)"
|
|
235
|
+
fi
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Migration: wavespeed.md moved from services/ai-generation/ to tools/video/ (v2.111+)
|
|
239
|
+
local old_wavespeed="$target_dir/services/ai-generation/wavespeed.md"
|
|
240
|
+
if [[ -f "$old_wavespeed" ]]; then
|
|
241
|
+
rm -f "$old_wavespeed"
|
|
242
|
+
rmdir "$target_dir/services/ai-generation" 2>/dev/null || true
|
|
243
|
+
print_info "Migrated wavespeed.md from services/ai-generation/ to tools/video/"
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# Deploy enabled plugins from plugins.json
|
|
247
|
+
deploy_plugins "$target_dir" "$plugins_file"
|
|
248
|
+
else
|
|
249
|
+
print_error "Failed to deploy agents"
|
|
250
|
+
return 1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
return 0
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
inject_agents_reference() {
|
|
257
|
+
print_info "Adding aidevops reference to AI assistant configurations..."
|
|
258
|
+
|
|
259
|
+
local reference_line='Add ~/.aidevops/agents/AGENTS.md to context for AI DevOps capabilities.'
|
|
260
|
+
|
|
261
|
+
# AI assistant agent directories - these receive AGENTS.md reference
|
|
262
|
+
# Format: "config_dir:agents_subdir" where agents_subdir is the folder containing agent files
|
|
263
|
+
# Only Claude Code (companion CLI) and .opencode are included here.
|
|
264
|
+
# OpenCode excluded: its agent/ dir treats every .md as a subagent, so AGENTS.md
|
|
265
|
+
# would show as a mode. OpenCode gets the reference via opencode.json instructions
|
|
266
|
+
# field and the config-root AGENTS.md (deployed by deploy_opencode_greeting below).
|
|
267
|
+
local ai_agent_dirs=(
|
|
268
|
+
"$HOME/.claude:commands"
|
|
269
|
+
"$HOME/.opencode:."
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
local updated_count=0
|
|
273
|
+
|
|
274
|
+
for entry in "${ai_agent_dirs[@]}"; do
|
|
275
|
+
local config_dir="${entry%%:*}"
|
|
276
|
+
local agents_subdir="${entry##*:}"
|
|
277
|
+
local agents_dir="$config_dir/$agents_subdir"
|
|
278
|
+
local agents_file="$agents_dir/AGENTS.md"
|
|
279
|
+
|
|
280
|
+
# Only process if the config directory exists (tool is installed)
|
|
281
|
+
if [[ -d "$config_dir" ]]; then
|
|
282
|
+
# Create agents subdirectory if needed
|
|
283
|
+
mkdir -p "$agents_dir"
|
|
284
|
+
|
|
285
|
+
# Check if AGENTS.md exists and has our reference
|
|
286
|
+
if [[ -f "$agents_file" ]]; then
|
|
287
|
+
# Check first line for our reference
|
|
288
|
+
local first_line
|
|
289
|
+
first_line=$(head -1 "$agents_file" 2>/dev/null || echo "")
|
|
290
|
+
if [[ "$first_line" != *"~/.aidevops/agents/AGENTS.md"* ]]; then
|
|
291
|
+
# Prepend reference to existing file
|
|
292
|
+
local temp_file
|
|
293
|
+
temp_file=$(mktemp)
|
|
294
|
+
trap 'rm -f "${temp_file:-}"' RETURN
|
|
295
|
+
echo "$reference_line" >"$temp_file"
|
|
296
|
+
echo "" >>"$temp_file"
|
|
297
|
+
cat "$agents_file" >>"$temp_file"
|
|
298
|
+
mv "$temp_file" "$agents_file"
|
|
299
|
+
print_success "Added reference to $agents_file"
|
|
300
|
+
((++updated_count))
|
|
301
|
+
else
|
|
302
|
+
print_info "Reference already exists in $agents_file"
|
|
303
|
+
fi
|
|
304
|
+
else
|
|
305
|
+
# Create new file with just the reference
|
|
306
|
+
echo "$reference_line" >"$agents_file"
|
|
307
|
+
print_success "Created $agents_file with aidevops reference"
|
|
308
|
+
((++updated_count))
|
|
309
|
+
fi
|
|
310
|
+
fi
|
|
311
|
+
done
|
|
312
|
+
|
|
313
|
+
if [[ $updated_count -eq 0 ]]; then
|
|
314
|
+
print_info "No AI assistant configs found to update (tools may not be installed yet)"
|
|
315
|
+
else
|
|
316
|
+
print_success "Updated $updated_count AI assistant configuration(s)"
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
# Clean up stale AGENTS.md from OpenCode agent dir (was incorrectly showing as subagent)
|
|
320
|
+
rm -f "$HOME/.config/opencode/agent/AGENTS.md"
|
|
321
|
+
|
|
322
|
+
# Deploy OpenCode config-level AGENTS.md from managed template
|
|
323
|
+
# This controls the session greeting (auto-loaded by OpenCode from config root)
|
|
324
|
+
local opencode_config_dir="$HOME/.config/opencode"
|
|
325
|
+
local opencode_config_agents="$opencode_config_dir/AGENTS.md"
|
|
326
|
+
local template_source="$INSTALL_DIR/templates/opencode-config-agents.md"
|
|
327
|
+
|
|
328
|
+
if [[ -d "$opencode_config_dir" && -f "$template_source" ]]; then
|
|
329
|
+
# Backup if file exists and differs from template
|
|
330
|
+
if [[ -f "$opencode_config_agents" ]]; then
|
|
331
|
+
if ! diff -q "$template_source" "$opencode_config_agents" &>/dev/null; then
|
|
332
|
+
create_backup_with_rotation "$opencode_config_agents" "opencode-agents"
|
|
333
|
+
fi
|
|
334
|
+
fi
|
|
335
|
+
if cp "$template_source" "$opencode_config_agents"; then
|
|
336
|
+
print_success "Deployed greeting template to $opencode_config_agents"
|
|
337
|
+
else
|
|
338
|
+
print_error "Failed to deploy greeting template to $opencode_config_agents"
|
|
339
|
+
fi
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
return 0
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
install_beads_binary() {
|
|
346
|
+
local os arch tarball_name
|
|
347
|
+
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
348
|
+
arch=$(uname -m)
|
|
349
|
+
|
|
350
|
+
# Map architecture names to Beads release naming convention
|
|
351
|
+
case "$arch" in
|
|
352
|
+
x86_64 | amd64) arch="amd64" ;;
|
|
353
|
+
aarch64 | arm64) arch="arm64" ;;
|
|
354
|
+
*)
|
|
355
|
+
print_warning "Unsupported architecture for Beads binary download: $arch"
|
|
356
|
+
return 1
|
|
357
|
+
;;
|
|
358
|
+
esac
|
|
359
|
+
|
|
360
|
+
# Get latest version tag from GitHub API
|
|
361
|
+
local latest_version
|
|
362
|
+
latest_version=$(curl -fsSL "https://api.github.com/repos/steveyegge/beads/releases/latest" 2>/dev/null |
|
|
363
|
+
grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"v\{0,1\}\([^"]*\)".*/\1/')
|
|
364
|
+
|
|
365
|
+
if [[ -z "$latest_version" ]]; then
|
|
366
|
+
print_warning "Could not determine latest Beads version from GitHub"
|
|
367
|
+
return 1
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
tarball_name="beads_${latest_version}_${os}_${arch}.tar.gz"
|
|
371
|
+
local download_url="https://github.com/steveyegge/beads/releases/download/v${latest_version}/${tarball_name}"
|
|
372
|
+
|
|
373
|
+
print_info "Downloading Beads CLI v${latest_version} (${os}/${arch})..."
|
|
374
|
+
|
|
375
|
+
local tmp_dir
|
|
376
|
+
tmp_dir=$(mktemp -d)
|
|
377
|
+
# shellcheck disable=SC2064 # Intentional: $tmp_dir must expand at trap definition time, not execution time
|
|
378
|
+
trap "rm -rf '$tmp_dir'" RETURN
|
|
379
|
+
|
|
380
|
+
if ! curl -fsSL "$download_url" -o "$tmp_dir/$tarball_name" 2>/dev/null; then
|
|
381
|
+
print_warning "Failed to download Beads binary from $download_url"
|
|
382
|
+
return 1
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
# Extract and install
|
|
386
|
+
if ! tar -xzf "$tmp_dir/$tarball_name" -C "$tmp_dir" 2>/dev/null; then
|
|
387
|
+
print_warning "Failed to extract Beads binary"
|
|
388
|
+
return 1
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
# Find the bd binary in the extracted files
|
|
392
|
+
local bd_binary
|
|
393
|
+
bd_binary=$(find "$tmp_dir" -name "bd" -type f 2>/dev/null | head -1)
|
|
394
|
+
if [[ -z "$bd_binary" ]]; then
|
|
395
|
+
print_warning "bd binary not found in downloaded archive"
|
|
396
|
+
return 1
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
# Install to a writable location
|
|
400
|
+
local install_dir="/usr/local/bin"
|
|
401
|
+
if [[ ! -w "$install_dir" ]]; then
|
|
402
|
+
if command -v sudo &>/dev/null; then
|
|
403
|
+
sudo install -m 755 "$bd_binary" "$install_dir/bd"
|
|
404
|
+
else
|
|
405
|
+
# Fallback to user-local bin
|
|
406
|
+
install_dir="$HOME/.local/bin"
|
|
407
|
+
mkdir -p "$install_dir"
|
|
408
|
+
install -m 755 "$bd_binary" "$install_dir/bd"
|
|
409
|
+
# Ensure ~/.local/bin is in PATH
|
|
410
|
+
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
|
411
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
412
|
+
print_info "Added ~/.local/bin to PATH for this session"
|
|
413
|
+
fi
|
|
414
|
+
fi
|
|
415
|
+
else
|
|
416
|
+
install -m 755 "$bd_binary" "$install_dir/bd"
|
|
417
|
+
fi
|
|
418
|
+
|
|
419
|
+
if command -v bd &>/dev/null; then
|
|
420
|
+
print_success "Beads CLI installed via binary download (v${latest_version})"
|
|
421
|
+
return 0
|
|
422
|
+
else
|
|
423
|
+
print_warning "Beads binary installed to $install_dir/bd but not found in PATH"
|
|
424
|
+
return 1
|
|
425
|
+
fi
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
install_beads_go() {
|
|
429
|
+
if ! command -v go &>/dev/null; then
|
|
430
|
+
return 1
|
|
431
|
+
fi
|
|
432
|
+
if run_with_spinner "Installing Beads via Go" go install github.com/steveyegge/beads/cmd/bd@latest; then
|
|
433
|
+
print_info "Ensure \$GOPATH/bin is in your PATH"
|
|
434
|
+
return 0
|
|
435
|
+
fi
|
|
436
|
+
print_warning "Go installation failed"
|
|
437
|
+
return 1
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
setup_beads() {
|
|
441
|
+
print_info "Setting up Beads (task graph visualization)..."
|
|
442
|
+
|
|
443
|
+
# Check if Beads CLI (bd) is already installed
|
|
444
|
+
if command -v bd &>/dev/null; then
|
|
445
|
+
local bd_version
|
|
446
|
+
bd_version=$(bd --version 2>/dev/null | head -1 || echo "unknown")
|
|
447
|
+
print_success "Beads CLI (bd) already installed: $bd_version"
|
|
448
|
+
else
|
|
449
|
+
# Try to install via Homebrew first (macOS/Linux with Homebrew)
|
|
450
|
+
if command -v brew &>/dev/null; then
|
|
451
|
+
if run_with_spinner "Installing Beads via Homebrew" brew install steveyegge/beads/bd; then
|
|
452
|
+
: # Success message handled by spinner
|
|
453
|
+
else
|
|
454
|
+
print_warning "Homebrew tap installation failed, trying alternative..."
|
|
455
|
+
install_beads_binary || install_beads_go
|
|
456
|
+
fi
|
|
457
|
+
elif command -v go &>/dev/null; then
|
|
458
|
+
if run_with_spinner "Installing Beads via Go" go install github.com/steveyegge/beads/cmd/bd@latest; then
|
|
459
|
+
print_info "Ensure \$GOPATH/bin is in your PATH"
|
|
460
|
+
else
|
|
461
|
+
print_warning "Go installation failed, trying binary download..."
|
|
462
|
+
install_beads_binary
|
|
463
|
+
fi
|
|
464
|
+
else
|
|
465
|
+
# No brew, no Go -- try binary download first, then offer Homebrew install
|
|
466
|
+
if ! install_beads_binary; then
|
|
467
|
+
# Binary download failed -- offer to install Homebrew (Linux only)
|
|
468
|
+
if ensure_homebrew; then
|
|
469
|
+
# Homebrew now available, retry via tap
|
|
470
|
+
if run_with_spinner "Installing Beads via Homebrew" brew install steveyegge/beads/bd; then
|
|
471
|
+
: # Success
|
|
472
|
+
else
|
|
473
|
+
print_warning "Homebrew tap installation failed"
|
|
474
|
+
fi
|
|
475
|
+
else
|
|
476
|
+
print_warning "Beads CLI (bd) not installed"
|
|
477
|
+
echo ""
|
|
478
|
+
echo " Install options:"
|
|
479
|
+
echo " Binary download: https://github.com/steveyegge/beads/releases"
|
|
480
|
+
echo " macOS/Linux (Homebrew): brew install steveyegge/beads/bd"
|
|
481
|
+
echo " Go: go install github.com/steveyegge/beads/cmd/bd@latest"
|
|
482
|
+
echo ""
|
|
483
|
+
fi
|
|
484
|
+
fi
|
|
485
|
+
fi
|
|
486
|
+
fi
|
|
487
|
+
|
|
488
|
+
print_info "Beads provides task graph visualization for TODO.md and PLANS.md"
|
|
489
|
+
print_info "After installation, run: aidevops init beads"
|
|
490
|
+
|
|
491
|
+
# Offer to install optional Beads UI tools
|
|
492
|
+
setup_beads_ui
|
|
493
|
+
|
|
494
|
+
return 0
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
setup_beads_ui() {
|
|
498
|
+
echo ""
|
|
499
|
+
print_info "Beads UI tools provide enhanced visualization:"
|
|
500
|
+
echo " • bv (Go) - PageRank, critical path, graph analytics TUI"
|
|
501
|
+
echo " • beads-ui (Node.js) - Web dashboard with live updates"
|
|
502
|
+
echo " • bdui (Node.js) - React/Ink terminal UI"
|
|
503
|
+
echo " • perles (Rust) - BQL query language TUI"
|
|
504
|
+
echo ""
|
|
505
|
+
|
|
506
|
+
read -r -p "Install optional Beads UI tools? [Y/n]: " install_beads_ui
|
|
507
|
+
|
|
508
|
+
if [[ ! "$install_beads_ui" =~ ^[Yy]?$ ]]; then
|
|
509
|
+
print_info "Skipped Beads UI tools (can install later from beads.md docs)"
|
|
510
|
+
return 0
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
local installed_count=0
|
|
514
|
+
|
|
515
|
+
# bv (beads_viewer) - Go TUI installed via Homebrew
|
|
516
|
+
# https://github.com/Dicklesworthstone/beads_viewer
|
|
517
|
+
read -r -p " Install bv (TUI with PageRank, critical path, graph analytics)? [Y/n]: " install_viewer
|
|
518
|
+
if [[ "$install_viewer" =~ ^[Yy]?$ ]]; then
|
|
519
|
+
if command -v brew &>/dev/null; then
|
|
520
|
+
# brew install user/tap/formula auto-taps
|
|
521
|
+
if run_with_spinner "Installing bv via Homebrew" brew install dicklesworthstone/tap/bv; then
|
|
522
|
+
print_info "Run: bv (in a beads-enabled project)"
|
|
523
|
+
((++installed_count))
|
|
524
|
+
else
|
|
525
|
+
print_warning "Homebrew install failed - try manually:"
|
|
526
|
+
print_info " brew install dicklesworthstone/tap/bv"
|
|
527
|
+
fi
|
|
528
|
+
else
|
|
529
|
+
# No Homebrew - try install script or Go
|
|
530
|
+
print_warning "Homebrew not found"
|
|
531
|
+
if command -v go &>/dev/null; then
|
|
532
|
+
# Go available - use go install
|
|
533
|
+
if run_with_spinner "Installing bv via Go" go install github.com/Dicklesworthstone/beads_viewer/cmd/bv@latest; then
|
|
534
|
+
print_info "Run: bv (in a beads-enabled project)"
|
|
535
|
+
((++installed_count))
|
|
536
|
+
else
|
|
537
|
+
print_warning "Go install failed"
|
|
538
|
+
fi
|
|
539
|
+
else
|
|
540
|
+
# Offer verified install script (download-then-execute, not piped)
|
|
541
|
+
read -r -p " Install bv via install script? [Y/n]: " use_script
|
|
542
|
+
if [[ "$use_script" =~ ^[Yy]?$ ]]; then
|
|
543
|
+
if verified_install "bv (beads viewer)" "https://raw.githubusercontent.com/Dicklesworthstone/beads_viewer/main/install.sh"; then
|
|
544
|
+
print_info "Run: bv (in a beads-enabled project)"
|
|
545
|
+
((++installed_count))
|
|
546
|
+
else
|
|
547
|
+
print_warning "Install script failed - try manually:"
|
|
548
|
+
print_info " Homebrew: brew tap dicklesworthstone/tap && brew install dicklesworthstone/tap/bv"
|
|
549
|
+
fi
|
|
550
|
+
else
|
|
551
|
+
print_info "Install later:"
|
|
552
|
+
print_info " Homebrew: brew tap dicklesworthstone/tap && brew install dicklesworthstone/tap/bv"
|
|
553
|
+
print_info " Go: go install github.com/Dicklesworthstone/beads_viewer/cmd/bv@latest"
|
|
554
|
+
fi
|
|
555
|
+
fi
|
|
556
|
+
fi
|
|
557
|
+
fi
|
|
558
|
+
|
|
559
|
+
# beads-ui (Node.js)
|
|
560
|
+
if command -v npm &>/dev/null; then
|
|
561
|
+
read -r -p " Install beads-ui (Web dashboard)? [Y/n]: " install_web
|
|
562
|
+
if [[ "$install_web" =~ ^[Yy]?$ ]]; then
|
|
563
|
+
if run_with_spinner "Installing beads-ui" npm_global_install beads-ui; then
|
|
564
|
+
print_info "Run: beads-ui"
|
|
565
|
+
((++installed_count))
|
|
566
|
+
fi
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
read -r -p " Install bdui (React/Ink TUI)? [Y/n]: " install_bdui
|
|
570
|
+
if [[ "$install_bdui" =~ ^[Yy]?$ ]]; then
|
|
571
|
+
if run_with_spinner "Installing bdui" npm_global_install bdui; then
|
|
572
|
+
print_info "Run: bdui"
|
|
573
|
+
((++installed_count))
|
|
574
|
+
fi
|
|
575
|
+
fi
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
# perles (Rust)
|
|
579
|
+
if command -v cargo &>/dev/null; then
|
|
580
|
+
read -r -p " Install perles (BQL query language TUI)? [Y/n]: " install_perles
|
|
581
|
+
if [[ "$install_perles" =~ ^[Yy]?$ ]]; then
|
|
582
|
+
if run_with_spinner "Installing perles (Rust compile)" cargo install perles; then
|
|
583
|
+
print_info "Run: perles"
|
|
584
|
+
((++installed_count))
|
|
585
|
+
fi
|
|
586
|
+
fi
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
if [[ $installed_count -gt 0 ]]; then
|
|
590
|
+
print_success "Installed $installed_count Beads UI tool(s)"
|
|
591
|
+
else
|
|
592
|
+
print_info "No Beads UI tools installed"
|
|
593
|
+
fi
|
|
594
|
+
|
|
595
|
+
echo ""
|
|
596
|
+
print_info "Beads UI documentation: ~/.aidevops/agents/tools/task-management/beads.md"
|
|
597
|
+
|
|
598
|
+
return 0
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
setup_safety_hooks() {
|
|
602
|
+
print_info "Setting up Claude Code safety hooks..."
|
|
603
|
+
|
|
604
|
+
# Check Python is available
|
|
605
|
+
if ! command -v python3 &>/dev/null; then
|
|
606
|
+
print_warning "Python 3 not found - safety hooks require Python 3"
|
|
607
|
+
return 0
|
|
608
|
+
fi
|
|
609
|
+
|
|
610
|
+
local helper_script="$HOME/.aidevops/agents/scripts/install-hooks-helper.sh"
|
|
611
|
+
if [[ ! -f "$helper_script" ]]; then
|
|
612
|
+
# Fall back to repo copy (INSTALL_DIR set by setup.sh)
|
|
613
|
+
helper_script="${INSTALL_DIR:-.}/.agents/scripts/install-hooks-helper.sh"
|
|
614
|
+
fi
|
|
615
|
+
|
|
616
|
+
if [[ ! -f "$helper_script" ]]; then
|
|
617
|
+
print_warning "install-hooks-helper.sh not found - skipping safety hooks"
|
|
618
|
+
return 0
|
|
619
|
+
fi
|
|
620
|
+
|
|
621
|
+
if bash "$helper_script" install; then
|
|
622
|
+
print_success "Claude Code safety hooks installed"
|
|
623
|
+
else
|
|
624
|
+
print_warning "Safety hook installation encountered issues (non-critical)"
|
|
625
|
+
fi
|
|
626
|
+
return 0
|
|
627
|
+
}
|