@prmichaelsen/acp-visualizer 0.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 +68 -0
- package/agent/commands/acp.clarification-address.md +417 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +437 -0
- package/agent/commands/acp.clarifications-research.md +326 -0
- package/agent/commands/acp.command-create.md +432 -0
- package/agent/commands/acp.design-create.md +286 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.handoff.md +270 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +546 -0
- package/agent/commands/acp.package-create.md +895 -0
- package/agent/commands/acp.package-info.md +212 -0
- package/agent/commands/acp.package-install.md +539 -0
- package/agent/commands/acp.package-list.md +280 -0
- package/agent/commands/acp.package-publish.md +541 -0
- package/agent/commands/acp.package-remove.md +293 -0
- package/agent/commands/acp.package-search.md +307 -0
- package/agent/commands/acp.package-update.md +361 -0
- package/agent/commands/acp.package-validate.md +540 -0
- package/agent/commands/acp.pattern-create.md +386 -0
- package/agent/commands/acp.plan.md +587 -0
- package/agent/commands/acp.proceed.md +882 -0
- package/agent/commands/acp.project-create.md +675 -0
- package/agent/commands/acp.project-info.md +312 -0
- package/agent/commands/acp.project-list.md +226 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +227 -0
- package/agent/commands/acp.project-update.md +307 -0
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +407 -0
- package/agent/commands/acp.resume.md +239 -0
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +293 -0
- package/agent/commands/acp.sync.md +364 -0
- package/agent/commands/acp.task-create.md +500 -0
- package/agent/commands/acp.update.md +302 -0
- package/agent/commands/acp.validate.md +466 -0
- package/agent/commands/acp.version-check-for-updates.md +276 -0
- package/agent/commands/acp.version-check.md +191 -0
- package/agent/commands/acp.version-update.md +289 -0
- package/agent/commands/command.template.md +339 -0
- package/agent/commands/git.commit.md +526 -0
- package/agent/commands/git.init.md +514 -0
- package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
- package/agent/commands/tanstack-cloudflare.tail.md +275 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/design.template.md +154 -0
- package/agent/design/local.dashboard-layout-routing.md +288 -0
- package/agent/design/local.data-model-yaml-parsing.md +310 -0
- package/agent/design/local.search-filtering.md +331 -0
- package/agent/design/local.server-api-auto-refresh.md +235 -0
- package/agent/design/local.table-tree-views.md +299 -0
- package/agent/design/local.visualizer-requirements.md +349 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/manifest.yaml +302 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
- package/agent/package.template.yaml +86 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +382 -0
- package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
- package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
- package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
- package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
- package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
- package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
- package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
- package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
- package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
- package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
- package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
- package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
- package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
- package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
- package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
- package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
- package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
- package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
- package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
- package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
- package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
- package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
- package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
- package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
- package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
- package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
- package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
- package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
- package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
- package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
- package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
- package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
- package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
- package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
- package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
- package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
- package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
- package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
- package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
- package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
- package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
- package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
- package/agent/progress.template.yaml +161 -0
- package/agent/progress.yaml +145 -0
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.common.sh +1781 -0
- package/agent/scripts/acp.install.sh +333 -0
- package/agent/scripts/acp.package-create.sh +924 -0
- package/agent/scripts/acp.package-info.sh +288 -0
- package/agent/scripts/acp.package-install.sh +893 -0
- package/agent/scripts/acp.package-list.sh +311 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +348 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +517 -0
- package/agent/scripts/acp.package-validate.sh +1018 -0
- package/agent/scripts/acp.uninstall.sh +85 -0
- package/agent/scripts/acp.version-check-for-updates.sh +98 -0
- package/agent/scripts/acp.version-check.sh +47 -0
- package/agent/scripts/acp.version-update.sh +176 -0
- package/agent/scripts/acp.yaml-parser.sh +985 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
- package/agent/tasks/task-1-{title}.template.md +244 -0
- package/bin/visualize.mjs +84 -0
- package/package.json +48 -0
- package/src/components/ExtraFieldsBadge.tsx +15 -0
- package/src/components/FilterBar.tsx +33 -0
- package/src/components/Header.tsx +23 -0
- package/src/components/MilestoneTable.tsx +167 -0
- package/src/components/MilestoneTree.tsx +84 -0
- package/src/components/ProgressBar.tsx +20 -0
- package/src/components/SearchInput.tsx +22 -0
- package/src/components/Sidebar.tsx +54 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/StatusDot.tsx +12 -0
- package/src/components/TaskList.tsx +36 -0
- package/src/components/ViewToggle.tsx +31 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/file-watcher.ts +43 -0
- package/src/lib/search.ts +48 -0
- package/src/lib/types.ts +73 -0
- package/src/lib/useAutoRefresh.ts +31 -0
- package/src/lib/useCollapse.ts +31 -0
- package/src/lib/useFilteredData.ts +55 -0
- package/src/lib/yaml-loader-real.spec.ts +47 -0
- package/src/lib/yaml-loader.spec.ts +201 -0
- package/src/lib/yaml-loader.ts +265 -0
- package/src/routeTree.gen.ts +140 -0
- package/src/router.tsx +10 -0
- package/src/routes/__root.tsx +75 -0
- package/src/routes/api/watch.ts +29 -0
- package/src/routes/index.tsx +115 -0
- package/src/routes/milestones.tsx +50 -0
- package/src/routes/search.tsx +84 -0
- package/src/routes/tasks.tsx +63 -0
- package/src/services/progress-database.service.ts +46 -0
- package/src/styles.css +25 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Agent Context Protocol (ACP) Package Install Script - OPTIMIZED VERSION
|
|
4
|
+
# Installs third-party ACP packages with batched operations for 10x+ performance improvement
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Source common utilities
|
|
9
|
+
SCRIPT_DIR="$(dirname "$0")"
|
|
10
|
+
. "${SCRIPT_DIR}/acp.common.sh"
|
|
11
|
+
. "${SCRIPT_DIR}/acp.yaml-parser.sh"
|
|
12
|
+
|
|
13
|
+
# Initialize colors
|
|
14
|
+
init_colors
|
|
15
|
+
|
|
16
|
+
# Parse arguments (same as original)
|
|
17
|
+
REPO_URL=""
|
|
18
|
+
INSTALL_PATTERNS=false
|
|
19
|
+
INSTALL_COMMANDS=false
|
|
20
|
+
INSTALL_DESIGNS=false
|
|
21
|
+
INSTALL_FILES=false
|
|
22
|
+
INSTALL_INDICES=false
|
|
23
|
+
PATTERN_FILES=()
|
|
24
|
+
COMMAND_FILES=()
|
|
25
|
+
DESIGN_FILES=()
|
|
26
|
+
FILE_FILES=()
|
|
27
|
+
INDEX_FILES=()
|
|
28
|
+
LIST_ONLY=false
|
|
29
|
+
GLOBAL_INSTALL=false
|
|
30
|
+
INSTALL_EXPERIMENTAL=false
|
|
31
|
+
SKIP_CONFIRM=false
|
|
32
|
+
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case $1 in
|
|
35
|
+
--repo)
|
|
36
|
+
REPO_URL="$2"
|
|
37
|
+
shift 2
|
|
38
|
+
;;
|
|
39
|
+
--global)
|
|
40
|
+
GLOBAL_INSTALL=true
|
|
41
|
+
shift
|
|
42
|
+
;;
|
|
43
|
+
--experimental)
|
|
44
|
+
INSTALL_EXPERIMENTAL=true
|
|
45
|
+
shift
|
|
46
|
+
;;
|
|
47
|
+
-y|--yes)
|
|
48
|
+
SKIP_CONFIRM=true
|
|
49
|
+
shift
|
|
50
|
+
;;
|
|
51
|
+
--patterns)
|
|
52
|
+
INSTALL_PATTERNS=true
|
|
53
|
+
shift
|
|
54
|
+
while [[ $# -gt 0 && ! $1 =~ ^- ]]; do
|
|
55
|
+
PATTERN_FILES+=("$1")
|
|
56
|
+
shift
|
|
57
|
+
done
|
|
58
|
+
;;
|
|
59
|
+
--commands)
|
|
60
|
+
INSTALL_COMMANDS=true
|
|
61
|
+
shift
|
|
62
|
+
while [[ $# -gt 0 && ! $1 =~ ^- ]]; do
|
|
63
|
+
COMMAND_FILES+=("$1")
|
|
64
|
+
shift
|
|
65
|
+
done
|
|
66
|
+
;;
|
|
67
|
+
--designs)
|
|
68
|
+
INSTALL_DESIGNS=true
|
|
69
|
+
shift
|
|
70
|
+
while [[ $# -gt 0 && ! $1 =~ ^- ]]; do
|
|
71
|
+
DESIGN_FILES+=("$1")
|
|
72
|
+
shift
|
|
73
|
+
done
|
|
74
|
+
;;
|
|
75
|
+
--files)
|
|
76
|
+
INSTALL_FILES=true
|
|
77
|
+
shift
|
|
78
|
+
while [[ $# -gt 0 && ! $1 =~ ^- ]]; do
|
|
79
|
+
FILE_FILES+=("$1")
|
|
80
|
+
shift
|
|
81
|
+
done
|
|
82
|
+
;;
|
|
83
|
+
--indices)
|
|
84
|
+
INSTALL_INDICES=true
|
|
85
|
+
shift
|
|
86
|
+
while [[ $# -gt 0 && ! $1 =~ ^- ]]; do
|
|
87
|
+
INDEX_FILES+=("$1")
|
|
88
|
+
shift
|
|
89
|
+
done
|
|
90
|
+
;;
|
|
91
|
+
--list)
|
|
92
|
+
LIST_ONLY=true
|
|
93
|
+
shift
|
|
94
|
+
;;
|
|
95
|
+
*)
|
|
96
|
+
echo "${RED}Error: Unknown option: $1${NC}"
|
|
97
|
+
echo "Use --repo to specify repository URL"
|
|
98
|
+
exit 1
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
101
|
+
done
|
|
102
|
+
|
|
103
|
+
# Check if repository URL provided
|
|
104
|
+
if [ -z "$REPO_URL" ]; then
|
|
105
|
+
echo "${RED}Error: Repository URL required${NC}"
|
|
106
|
+
echo "Usage: $0 --repo <repository-url> [options]"
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Default: install everything if no selective flags specified
|
|
111
|
+
if [[ "$INSTALL_PATTERNS" == false && "$INSTALL_COMMANDS" == false && "$INSTALL_DESIGNS" == false && "$INSTALL_FILES" == false && "$INSTALL_INDICES" == false ]]; then
|
|
112
|
+
INSTALL_PATTERNS=true
|
|
113
|
+
INSTALL_COMMANDS=true
|
|
114
|
+
INSTALL_DESIGNS=true
|
|
115
|
+
INSTALL_FILES=true
|
|
116
|
+
INSTALL_INDICES=true
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo "${BLUE}📦 ACP Package Installer (Optimized)${NC}"
|
|
120
|
+
echo "========================================"
|
|
121
|
+
echo ""
|
|
122
|
+
echo "Repository: $REPO_URL"
|
|
123
|
+
echo ""
|
|
124
|
+
|
|
125
|
+
# Validate URL format
|
|
126
|
+
if [[ ! "$REPO_URL" =~ ^https?:// ]] && [[ ! "$REPO_URL" =~ ^file:// ]] && [[ ! -d "$REPO_URL" ]]; then
|
|
127
|
+
echo "${RED}Error: Invalid repository URL${NC}"
|
|
128
|
+
exit 1
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Create temporary directory
|
|
132
|
+
TEMP_DIR=$(mktemp -d)
|
|
133
|
+
trap "rm -rf $TEMP_DIR" EXIT
|
|
134
|
+
|
|
135
|
+
echo "Cloning repository..."
|
|
136
|
+
if [ -d "$REPO_URL" ]; then
|
|
137
|
+
# Local directory - copy contents instead of clone
|
|
138
|
+
cp -r "$REPO_URL"/* "$TEMP_DIR/" 2>/dev/null || cp -r "$REPO_URL"/.[!.]* "$TEMP_DIR/" 2>/dev/null || true
|
|
139
|
+
echo "${GREEN}✓${NC} Local directory copied"
|
|
140
|
+
elif ! git clone --depth 1 "$REPO_URL" "$TEMP_DIR" &>/dev/null; then
|
|
141
|
+
echo "${RED}Error: Failed to clone repository${NC}"
|
|
142
|
+
exit 1
|
|
143
|
+
else
|
|
144
|
+
echo "${GREEN}✓${NC} Repository cloned"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
echo "${GREEN}✓${NC} Repository cloned"
|
|
148
|
+
echo ""
|
|
149
|
+
|
|
150
|
+
# Check for agent/ directory
|
|
151
|
+
if [ ! -d "$TEMP_DIR/agent" ]; then
|
|
152
|
+
echo "${RED}Error: No agent/ directory found${NC}"
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Determine installation directory and manifest
|
|
157
|
+
if [ "$GLOBAL_INSTALL" = true ]; then
|
|
158
|
+
INSTALL_BASE_DIR="$HOME/.acp/agent"
|
|
159
|
+
MANIFEST_FILE="$HOME/.acp/agent/manifest.yaml"
|
|
160
|
+
echo "${BLUE}Installing globally to ~/.acp/agent/${NC}"
|
|
161
|
+
echo ""
|
|
162
|
+
init_global_acp || {
|
|
163
|
+
echo "${RED}Error: Failed to initialize global infrastructure${NC}" >&2
|
|
164
|
+
exit 1
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
INSTALL_BASE_DIR="./agent"
|
|
168
|
+
MANIFEST_FILE="./agent/manifest.yaml"
|
|
169
|
+
echo "${BLUE}Installing locally to ./agent/${NC}"
|
|
170
|
+
echo ""
|
|
171
|
+
init_manifest
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Parse package metadata
|
|
175
|
+
parse_package_metadata "$TEMP_DIR"
|
|
176
|
+
COMMIT_HASH=$(get_commit_hash "$TEMP_DIR")
|
|
177
|
+
info "Commit: $COMMIT_HASH"
|
|
178
|
+
echo ""
|
|
179
|
+
|
|
180
|
+
# Validate dependencies
|
|
181
|
+
if [ -f "$TEMP_DIR/package.yaml" ]; then
|
|
182
|
+
if ! validate_project_dependencies "$TEMP_DIR/package.yaml"; then
|
|
183
|
+
echo "${RED}Installation cancelled due to dependency issues${NC}"
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Directories to install from
|
|
189
|
+
INSTALL_DIRS=()
|
|
190
|
+
[ "$INSTALL_PATTERNS" = true ] && INSTALL_DIRS+=("patterns")
|
|
191
|
+
[ "$INSTALL_COMMANDS" = true ] && INSTALL_DIRS+=("commands")
|
|
192
|
+
[ "$INSTALL_DESIGNS" = true ] && INSTALL_DIRS+=("design")
|
|
193
|
+
[ "$INSTALL_COMMANDS" = true ] && INSTALL_DIRS+=("scripts")
|
|
194
|
+
[ "$INSTALL_FILES" = true ] && INSTALL_DIRS+=("files")
|
|
195
|
+
[ "$INSTALL_INDICES" = true ] && INSTALL_DIRS+=("index")
|
|
196
|
+
|
|
197
|
+
# Mapping from dir names to manifest keys (dir → manifest key)
|
|
198
|
+
declare -A MANIFEST_KEYS=(
|
|
199
|
+
["patterns"]="patterns"
|
|
200
|
+
["commands"]="commands"
|
|
201
|
+
["design"]="designs"
|
|
202
|
+
["scripts"]="scripts"
|
|
203
|
+
["files"]="files"
|
|
204
|
+
["index"]="indices"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# ============================================================================
|
|
208
|
+
# OPTIMIZATION: Collect all files first, then batch process
|
|
209
|
+
# ============================================================================
|
|
210
|
+
|
|
211
|
+
# Arrays to hold all files to install
|
|
212
|
+
declare -A ALL_FILES_TO_INSTALL # Key: dir, Value: space-separated file paths
|
|
213
|
+
declare -A FILE_METADATA # Key: "dir/filename", Value: "version|experimental"
|
|
214
|
+
|
|
215
|
+
# Track installed commands for script-command binding resolution
|
|
216
|
+
INSTALLED_COMMANDS=()
|
|
217
|
+
|
|
218
|
+
INSTALLED_COUNT=0
|
|
219
|
+
SKIPPED_COUNT=0
|
|
220
|
+
|
|
221
|
+
# Template file metadata (populated during scanning when contents.files exists)
|
|
222
|
+
declare -A FILE_TARGETS # Key: "files/relpath", Value: target directory path
|
|
223
|
+
declare -A FILE_VARS # Key: "files/relpath", Value: "VAR1,VAR2,..."
|
|
224
|
+
declare -A COLLECTED_VARS # Key: "VARNAME", Value: user-provided value
|
|
225
|
+
HAS_FILE_METADATA=false
|
|
226
|
+
|
|
227
|
+
echo "Scanning for installable files..."
|
|
228
|
+
echo ""
|
|
229
|
+
|
|
230
|
+
# Parse package.yaml once for experimental checking
|
|
231
|
+
if [ -f "$TEMP_DIR/package.yaml" ]; then
|
|
232
|
+
yaml_parse "$TEMP_DIR/package.yaml"
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# Collect all files to install
|
|
236
|
+
for dir in "${INSTALL_DIRS[@]}"; do
|
|
237
|
+
SOURCE_DIR="$TEMP_DIR/agent/$dir"
|
|
238
|
+
|
|
239
|
+
if [ ! -d "$SOURCE_DIR" ]; then
|
|
240
|
+
continue
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
# Determine which files to process
|
|
244
|
+
declare -n FILE_LIST
|
|
245
|
+
case "$dir" in
|
|
246
|
+
patterns) FILE_LIST=PATTERN_FILES ;;
|
|
247
|
+
commands) FILE_LIST=COMMAND_FILES ;;
|
|
248
|
+
design) FILE_LIST=DESIGN_FILES ;;
|
|
249
|
+
scripts) FILE_LIST=COMMAND_FILES ;;
|
|
250
|
+
files) FILE_LIST=FILE_FILES ;;
|
|
251
|
+
index) FILE_LIST=INDEX_FILES ;;
|
|
252
|
+
esac
|
|
253
|
+
|
|
254
|
+
# Collect files
|
|
255
|
+
FILES_TO_PROCESS=()
|
|
256
|
+
if [ ${#FILE_LIST[@]} -gt 0 ]; then
|
|
257
|
+
# Selective installation
|
|
258
|
+
for file_name in "${FILE_LIST[@]}"; do
|
|
259
|
+
if [ "$dir" = "scripts" ]; then
|
|
260
|
+
[[ "$file_name" != *.sh ]] && file_name="${file_name}.sh"
|
|
261
|
+
elif [ "$dir" != "files" ]; then
|
|
262
|
+
[[ "$file_name" != *.md ]] && file_name="${file_name}.md"
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
file_path="$SOURCE_DIR/$file_name"
|
|
266
|
+
if [ -f "$file_path" ]; then
|
|
267
|
+
FILES_TO_PROCESS+=("$file_path")
|
|
268
|
+
|
|
269
|
+
# For selective files, also collect metadata from package.yaml
|
|
270
|
+
if [ "$dir" = "files" ] && [ -f "$TEMP_DIR/package.yaml" ]; then
|
|
271
|
+
_sel_idx=0
|
|
272
|
+
while true; do
|
|
273
|
+
_sel_name=$(yaml_query ".contents.files[$_sel_idx].name" 2>/dev/null || echo "")
|
|
274
|
+
[ -z "$_sel_name" ] || [ "$_sel_name" = "null" ] && break
|
|
275
|
+
if [ "$_sel_name" = "$file_name" ]; then
|
|
276
|
+
HAS_FILE_METADATA=true
|
|
277
|
+
_sel_target=$(yaml_query ".contents.files[$_sel_idx].target" 2>/dev/null || echo "")
|
|
278
|
+
[ -n "$_sel_target" ] && [ "$_sel_target" != "null" ] && FILE_TARGETS["files/$file_name"]="$_sel_target"
|
|
279
|
+
_sel_var_idx=0
|
|
280
|
+
_sel_vars=""
|
|
281
|
+
while true; do
|
|
282
|
+
_sel_var=$(yaml_query ".contents.files[$_sel_idx].variables[$_sel_var_idx]" 2>/dev/null || echo "")
|
|
283
|
+
[ -z "$_sel_var" ] || [ "$_sel_var" = "null" ] && break
|
|
284
|
+
[ -n "$_sel_vars" ] && _sel_vars="$_sel_vars,$_sel_var" || _sel_vars="$_sel_var"
|
|
285
|
+
_sel_var_idx=$((_sel_var_idx + 1))
|
|
286
|
+
done
|
|
287
|
+
[ -n "$_sel_vars" ] && FILE_VARS["files/$file_name"]="$_sel_vars"
|
|
288
|
+
break
|
|
289
|
+
fi
|
|
290
|
+
_sel_idx=$((_sel_idx + 1))
|
|
291
|
+
done
|
|
292
|
+
fi
|
|
293
|
+
else
|
|
294
|
+
echo "${YELLOW}⚠${NC} File not found in $dir/: $file_name"
|
|
295
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
296
|
+
fi
|
|
297
|
+
done
|
|
298
|
+
else
|
|
299
|
+
# Install all files
|
|
300
|
+
if [ "$dir" = "files" ]; then
|
|
301
|
+
# Check if package.yaml has contents.files metadata
|
|
302
|
+
_first_file=$(yaml_query ".contents.files[0].name" 2>/dev/null || echo "")
|
|
303
|
+
if [ -f "$TEMP_DIR/package.yaml" ] && [ -n "$_first_file" ] && [ "$_first_file" != "null" ]; then
|
|
304
|
+
# Use package.yaml contents.files as source of truth
|
|
305
|
+
HAS_FILE_METADATA=true
|
|
306
|
+
_file_idx=0
|
|
307
|
+
while true; do
|
|
308
|
+
_fname=$(yaml_query ".contents.files[$_file_idx].name" 2>/dev/null || echo "")
|
|
309
|
+
[ -z "$_fname" ] || [ "$_fname" = "null" ] && break
|
|
310
|
+
|
|
311
|
+
if [ -f "$SOURCE_DIR/$_fname" ]; then
|
|
312
|
+
FILES_TO_PROCESS+=("$SOURCE_DIR/$_fname")
|
|
313
|
+
|
|
314
|
+
# Store target metadata
|
|
315
|
+
_target=$(yaml_query ".contents.files[$_file_idx].target" 2>/dev/null || echo "")
|
|
316
|
+
[ -n "$_target" ] && [ "$_target" != "null" ] && FILE_TARGETS["files/$_fname"]="$_target"
|
|
317
|
+
|
|
318
|
+
# Collect variable names
|
|
319
|
+
_var_idx=0
|
|
320
|
+
_vars=""
|
|
321
|
+
while true; do
|
|
322
|
+
_var=$(yaml_query ".contents.files[$_file_idx].variables[$_var_idx]" 2>/dev/null || echo "")
|
|
323
|
+
[ -z "$_var" ] || [ "$_var" = "null" ] && break
|
|
324
|
+
[ -n "$_vars" ] && _vars="$_vars,$_var" || _vars="$_var"
|
|
325
|
+
_var_idx=$((_var_idx + 1))
|
|
326
|
+
done
|
|
327
|
+
[ -n "$_vars" ] && FILE_VARS["files/$_fname"]="$_vars"
|
|
328
|
+
else
|
|
329
|
+
echo " ${YELLOW}⚠${NC} Declared in package.yaml but not found: agent/files/$_fname"
|
|
330
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
_file_idx=$((_file_idx + 1))
|
|
334
|
+
done
|
|
335
|
+
else
|
|
336
|
+
# Fallback: recursive scan (backward compat for packages without contents.files)
|
|
337
|
+
while IFS= read -r file; do
|
|
338
|
+
[ -n "$file" ] && FILES_TO_PROCESS+=("$file")
|
|
339
|
+
done < <(find "$SOURCE_DIR" -type f)
|
|
340
|
+
fi
|
|
341
|
+
elif [ "$dir" = "scripts" ]; then
|
|
342
|
+
while IFS= read -r file; do
|
|
343
|
+
[ -n "$file" ] && FILES_TO_PROCESS+=("$file")
|
|
344
|
+
done < <(find "$SOURCE_DIR" -maxdepth 1 -name "*.sh" ! -name "*.template.sh" -type f)
|
|
345
|
+
elif [ "$dir" = "index" ]; then
|
|
346
|
+
while IFS= read -r file; do
|
|
347
|
+
[ -n "$file" ] && FILES_TO_PROCESS+=("$file")
|
|
348
|
+
done < <(find "$SOURCE_DIR" -maxdepth 1 -name "*.yaml" ! -name "*.template.yaml" -type f)
|
|
349
|
+
else
|
|
350
|
+
while IFS= read -r file; do
|
|
351
|
+
[ -n "$file" ] && FILES_TO_PROCESS+=("$file")
|
|
352
|
+
done < <(find "$SOURCE_DIR" -maxdepth 1 -name "*.md" ! -name "*.template.md" -type f)
|
|
353
|
+
fi
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
if [ ${#FILES_TO_PROCESS[@]} -eq 0 ]; then
|
|
357
|
+
unset -n FILE_LIST
|
|
358
|
+
continue
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
if [ "$dir" = "files" ] && [ "$HAS_FILE_METADATA" = false ]; then
|
|
362
|
+
echo "${BLUE}📁 $dir/${NC} (${#FILES_TO_PROCESS[@]} file(s)) → installs to ./"
|
|
363
|
+
else
|
|
364
|
+
echo "${BLUE}📁 $dir/${NC} (${#FILES_TO_PROCESS[@]} file(s))"
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
# Validate files
|
|
368
|
+
VALID_FILES=()
|
|
369
|
+
for file in "${FILES_TO_PROCESS[@]}"; do
|
|
370
|
+
# For files/ dir, use relative path from SOURCE_DIR; otherwise basename
|
|
371
|
+
if [ "$dir" = "files" ]; then
|
|
372
|
+
filename="${file#$SOURCE_DIR/}"
|
|
373
|
+
else
|
|
374
|
+
filename=$(basename "$file")
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
# Validation (not applied to files/ directory)
|
|
378
|
+
if [ "$dir" = "commands" ]; then
|
|
379
|
+
if [[ "$filename" =~ ^acp\. ]]; then
|
|
380
|
+
echo " ${RED}✗${NC} $filename (reserved namespace 'acp')"
|
|
381
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
382
|
+
continue
|
|
383
|
+
fi
|
|
384
|
+
if ! grep -q "🤖 Agent Directive" "$file"; then
|
|
385
|
+
echo " ${YELLOW}⚠${NC} $filename (missing agent directive - skipping)"
|
|
386
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
387
|
+
continue
|
|
388
|
+
fi
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
if [ "$dir" = "scripts" ]; then
|
|
392
|
+
if [[ "$filename" =~ ^acp\. ]]; then
|
|
393
|
+
echo " ${RED}✗${NC} $filename (reserved namespace 'acp')"
|
|
394
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
395
|
+
continue
|
|
396
|
+
fi
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
# Check experimental status
|
|
400
|
+
is_experimental=""
|
|
401
|
+
if [ -f "$TEMP_DIR/package.yaml" ]; then
|
|
402
|
+
if [ "$dir" = "files" ] && [ "$HAS_FILE_METADATA" = false ]; then
|
|
403
|
+
: # No per-file experimental marking without metadata
|
|
404
|
+
elif [ "$dir" = "files" ]; then
|
|
405
|
+
# Files entries have more fields (name, description, target, required, experimental)
|
|
406
|
+
# so need a wider context window (-A 6) to catch experimental: true
|
|
407
|
+
is_experimental=$(grep -A 1000 "^ ${dir}:" "$TEMP_DIR/package.yaml" 2>/dev/null | grep -A 6 "name: ${filename}" | grep "^ *experimental: true" | grep -v "^[[:space:]]*#" | head -1)
|
|
408
|
+
else
|
|
409
|
+
is_experimental=$(grep -A 1000 "^ ${dir}:" "$TEMP_DIR/package.yaml" 2>/dev/null | grep -A 2 "name: ${filename}" | grep "^ *experimental: true" | grep -v "^[[:space:]]*#" | head -1)
|
|
410
|
+
fi
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
if [ -n "$is_experimental" ] && [ "$INSTALL_EXPERIMENTAL" = false ]; then
|
|
414
|
+
echo " ${DIM}⊘${NC} $filename (experimental - use --experimental)"
|
|
415
|
+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
|
|
416
|
+
continue
|
|
417
|
+
fi
|
|
418
|
+
|
|
419
|
+
# Get file version
|
|
420
|
+
FILE_VERSION=$(get_file_version "$TEMP_DIR/package.yaml" "$dir" "$filename")
|
|
421
|
+
|
|
422
|
+
# Store metadata
|
|
423
|
+
FILE_METADATA["$dir/$filename"]="$FILE_VERSION|$is_experimental"
|
|
424
|
+
|
|
425
|
+
# Add to valid files
|
|
426
|
+
VALID_FILES+=("$file")
|
|
427
|
+
|
|
428
|
+
# Determine target path for overwrite check
|
|
429
|
+
if [ "$dir" = "files" ] && [ "$HAS_FILE_METADATA" = true ]; then
|
|
430
|
+
_file_target="${FILE_TARGETS[files/$filename]:-./}"
|
|
431
|
+
_bname=$(basename "$filename")
|
|
432
|
+
_bname="${_bname%.template}"
|
|
433
|
+
target_path="${_file_target}${_bname}"
|
|
434
|
+
elif [ "$dir" = "files" ]; then
|
|
435
|
+
target_path="./$filename"
|
|
436
|
+
else
|
|
437
|
+
target_path="$INSTALL_BASE_DIR/$dir/$filename"
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
# Build display info for files with metadata
|
|
441
|
+
_display_extra=""
|
|
442
|
+
if [ "$dir" = "files" ] && [ "$HAS_FILE_METADATA" = true ]; then
|
|
443
|
+
_file_vars="${FILE_VARS[files/$filename]:-}"
|
|
444
|
+
_display_extra=" → $target_path"
|
|
445
|
+
[ -n "$_file_vars" ] && _display_extra="$_display_extra (variables: $_file_vars)"
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
if [ -f "$target_path" ]; then
|
|
449
|
+
echo " ${YELLOW}⚠${NC} $filename${_display_extra} (will overwrite)"
|
|
450
|
+
else
|
|
451
|
+
echo " ${GREEN}✓${NC} $filename${_display_extra}"
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
INSTALLED_COUNT=$((INSTALLED_COUNT + 1))
|
|
455
|
+
done
|
|
456
|
+
|
|
457
|
+
# Store valid files for this directory
|
|
458
|
+
if [ ${#VALID_FILES[@]} -gt 0 ]; then
|
|
459
|
+
ALL_FILES_TO_INSTALL["$dir"]="${VALID_FILES[*]}"
|
|
460
|
+
fi
|
|
461
|
+
|
|
462
|
+
unset -n FILE_LIST
|
|
463
|
+
echo ""
|
|
464
|
+
done
|
|
465
|
+
|
|
466
|
+
# Warn about unrecognized directories in the package
|
|
467
|
+
KNOWN_DIRS="patterns commands design scripts files index"
|
|
468
|
+
if [ -d "$TEMP_DIR/agent" ]; then
|
|
469
|
+
UNRECOGNIZED=()
|
|
470
|
+
while IFS= read -r pkg_dir; do
|
|
471
|
+
dir_name=$(basename "$pkg_dir")
|
|
472
|
+
if ! echo " $KNOWN_DIRS " | grep -q " $dir_name "; then
|
|
473
|
+
UNRECOGNIZED+=("$dir_name")
|
|
474
|
+
fi
|
|
475
|
+
done < <(find "$TEMP_DIR/agent" -mindepth 1 -maxdepth 1 -type d)
|
|
476
|
+
|
|
477
|
+
if [ ${#UNRECOGNIZED[@]} -gt 0 ]; then
|
|
478
|
+
echo "${YELLOW}⚠ Unrecognized directories in package (not installed):${NC}"
|
|
479
|
+
for udir in "${UNRECOGNIZED[@]}"; do
|
|
480
|
+
echo " $udir/"
|
|
481
|
+
done
|
|
482
|
+
echo ""
|
|
483
|
+
fi
|
|
484
|
+
fi
|
|
485
|
+
|
|
486
|
+
# Exit if nothing to install
|
|
487
|
+
if [ $INSTALLED_COUNT -eq 0 ]; then
|
|
488
|
+
echo "${RED}Error: No valid files to install${NC}"
|
|
489
|
+
[ $SKIPPED_COUNT -gt 0 ] && echo "Skipped $SKIPPED_COUNT file(s)"
|
|
490
|
+
exit 1
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
# Confirm installation
|
|
494
|
+
echo "Ready to install $INSTALLED_COUNT file(s)"
|
|
495
|
+
[ $SKIPPED_COUNT -gt 0 ] && echo "($SKIPPED_COUNT file(s) will be skipped)"
|
|
496
|
+
echo ""
|
|
497
|
+
|
|
498
|
+
if [ "$LIST_ONLY" = true ]; then
|
|
499
|
+
echo "${BLUE}(dry run — no files were installed)${NC}"
|
|
500
|
+
exit 0
|
|
501
|
+
fi
|
|
502
|
+
|
|
503
|
+
if [ "$SKIP_CONFIRM" = false ]; then
|
|
504
|
+
read -p "Proceed with installation? (y/N) " -n 1 -r
|
|
505
|
+
echo
|
|
506
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
507
|
+
echo "Installation cancelled."
|
|
508
|
+
exit 0
|
|
509
|
+
fi
|
|
510
|
+
else
|
|
511
|
+
echo "Auto-confirming installation (-y flag)"
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
echo ""
|
|
515
|
+
|
|
516
|
+
# Collect template variables from user (if any files have variables declared)
|
|
517
|
+
if [ ${#FILE_VARS[@]} -gt 0 ]; then
|
|
518
|
+
echo "${BLUE}Collecting template variables...${NC}"
|
|
519
|
+
|
|
520
|
+
# Build list of unique variables across all templates
|
|
521
|
+
_all_vars=""
|
|
522
|
+
for _key in "${!FILE_VARS[@]}"; do
|
|
523
|
+
IFS=',' read -ra _var_arr <<< "${FILE_VARS[$_key]}"
|
|
524
|
+
for _var in "${_var_arr[@]}"; do
|
|
525
|
+
if [[ ! ",$_all_vars," =~ ",$_var," ]]; then
|
|
526
|
+
[ -n "$_all_vars" ] && _all_vars="$_all_vars,$_var" || _all_vars="$_var"
|
|
527
|
+
fi
|
|
528
|
+
done
|
|
529
|
+
done
|
|
530
|
+
|
|
531
|
+
# Prompt for each unique variable
|
|
532
|
+
IFS=',' read -ra _unique_vars <<< "$_all_vars"
|
|
533
|
+
for _var in "${_unique_vars[@]}"; do
|
|
534
|
+
read -p " Enter $_var: " _value
|
|
535
|
+
COLLECTED_VARS["$_var"]="$_value"
|
|
536
|
+
done
|
|
537
|
+
|
|
538
|
+
echo "${GREEN}✓${NC} Variables collected"
|
|
539
|
+
echo ""
|
|
540
|
+
fi
|
|
541
|
+
|
|
542
|
+
echo "Installing files..."
|
|
543
|
+
|
|
544
|
+
# ============================================================================
|
|
545
|
+
# OPTIMIZATION: Batch file operations
|
|
546
|
+
# ============================================================================
|
|
547
|
+
|
|
548
|
+
# Check if file should be installed based on experimental status
|
|
549
|
+
should_install_file() {
|
|
550
|
+
local filename="$1"
|
|
551
|
+
local file_type="$2" # commands, patterns, designs, scripts
|
|
552
|
+
|
|
553
|
+
# If no package.yaml, install everything
|
|
554
|
+
if [ ! -f "$TEMP_DIR/package.yaml" ]; then
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
|
|
558
|
+
# Check if file is marked experimental in package.yaml
|
|
559
|
+
# Extract only the relevant section, then find the specific entry
|
|
560
|
+
local section=$(grep -A 1000 "^ ${file_type}:" "$TEMP_DIR/package.yaml" 2>/dev/null | grep -B 1000 "^ [a-z]" 2>/dev/null | head -n -1 || true)
|
|
561
|
+
local is_experimental=$(echo "$section" | grep -A 3 "^ - name: ${filename}$" 2>/dev/null | grep "^ *experimental: true" 2>/dev/null | grep -v "^[[:space:]]*#" | head -1 || true)
|
|
562
|
+
|
|
563
|
+
if [ -n "$is_experimental" ]; then
|
|
564
|
+
if [ "$INSTALL_EXPERIMENTAL" = true ]; then
|
|
565
|
+
echo " ${YELLOW}⚠${NC} Installing experimental: ${filename}"
|
|
566
|
+
return 0 # Install it
|
|
567
|
+
else
|
|
568
|
+
echo " ${DIM}⊘${NC} Skipping experimental: ${filename} (use --experimental to install)"
|
|
569
|
+
return 1 # Skip it
|
|
570
|
+
fi
|
|
571
|
+
fi
|
|
572
|
+
|
|
573
|
+
return 0 # Install non-experimental files
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
# Add package to manifest once
|
|
577
|
+
add_package_to_manifest "$PACKAGE_NAME" "$REPO_URL" "$PACKAGE_VERSION" "$COMMIT_HASH"
|
|
578
|
+
|
|
579
|
+
# Batch copy all files (skip scripts — handled via script-command binding below)
|
|
580
|
+
for dir in "${!ALL_FILES_TO_INSTALL[@]}"; do
|
|
581
|
+
SOURCE_DIR="$TEMP_DIR/agent/$dir"
|
|
582
|
+
|
|
583
|
+
# Skip scripts in first pass — install selectively after commands via script-command binding
|
|
584
|
+
if [ "$dir" = "scripts" ]; then
|
|
585
|
+
continue
|
|
586
|
+
fi
|
|
587
|
+
|
|
588
|
+
# Copy all files
|
|
589
|
+
for file in ${ALL_FILES_TO_INSTALL[$dir]}; do
|
|
590
|
+
if [ "$dir" = "files" ]; then
|
|
591
|
+
rel_path="${file#$SOURCE_DIR/}"
|
|
592
|
+
|
|
593
|
+
if [ "$HAS_FILE_METADATA" = true ]; then
|
|
594
|
+
# Metadata-aware installation: use target path and variable substitution
|
|
595
|
+
_file_target="${FILE_TARGETS[files/$rel_path]:-./}"
|
|
596
|
+
_bname=$(basename "$rel_path")
|
|
597
|
+
_bname="${_bname%.template}"
|
|
598
|
+
_dest="${_file_target}${_bname}"
|
|
599
|
+
|
|
600
|
+
# Safety validation: reject paths that escape project root
|
|
601
|
+
if [[ "$_dest" =~ \.\. ]] || [[ "$_dest" =~ ^/ ]]; then
|
|
602
|
+
echo " ${RED}✗${NC} Skipping $rel_path (unsafe target: $_dest)"
|
|
603
|
+
continue
|
|
604
|
+
fi
|
|
605
|
+
|
|
606
|
+
mkdir -p "$(dirname "$_dest")"
|
|
607
|
+
|
|
608
|
+
# Apply variable substitution if template has variables
|
|
609
|
+
_file_vars="${FILE_VARS[files/$rel_path]:-}"
|
|
610
|
+
if [ -n "$_file_vars" ] && [ ${#COLLECTED_VARS[@]} -gt 0 ]; then
|
|
611
|
+
cp "$file" "$_dest"
|
|
612
|
+
IFS=',' read -ra _var_arr <<< "$_file_vars"
|
|
613
|
+
for _var in "${_var_arr[@]}"; do
|
|
614
|
+
_value="${COLLECTED_VARS[$_var]:-}"
|
|
615
|
+
if [ -n "$_value" ]; then
|
|
616
|
+
_escaped=$(printf '%s\n' "$_value" | sed 's/[&/\]/\\&/g')
|
|
617
|
+
_sed_i "s|{{${_var}}}|${_escaped}|g" "$_dest"
|
|
618
|
+
fi
|
|
619
|
+
done
|
|
620
|
+
else
|
|
621
|
+
cp "$file" "$_dest"
|
|
622
|
+
fi
|
|
623
|
+
else
|
|
624
|
+
# Backward compat: install to project root preserving subdirectory structure
|
|
625
|
+
target_dir="$(dirname "./$rel_path")"
|
|
626
|
+
mkdir -p "$target_dir"
|
|
627
|
+
cp "$file" "./$rel_path"
|
|
628
|
+
fi
|
|
629
|
+
else
|
|
630
|
+
mkdir -p "$INSTALL_BASE_DIR/$dir"
|
|
631
|
+
filename=$(basename "$file")
|
|
632
|
+
cp "$file" "$INSTALL_BASE_DIR/$dir/$filename"
|
|
633
|
+
fi
|
|
634
|
+
|
|
635
|
+
# Track installed commands for script dependency resolution
|
|
636
|
+
if [ "$dir" = "commands" ]; then
|
|
637
|
+
filename=$(basename "$file")
|
|
638
|
+
INSTALLED_COMMANDS+=("$filename")
|
|
639
|
+
fi
|
|
640
|
+
done
|
|
641
|
+
done
|
|
642
|
+
|
|
643
|
+
# ============================================================================
|
|
644
|
+
# Script-Command Binding: Install scripts based on command dependencies
|
|
645
|
+
# ============================================================================
|
|
646
|
+
|
|
647
|
+
if [ -f "$TEMP_DIR/package.yaml" ] && [ ${#INSTALLED_COMMANDS[@]} -gt 0 ]; then
|
|
648
|
+
echo "Resolving script dependencies..."
|
|
649
|
+
echo " Installed commands: ${INSTALLED_COMMANDS[@]}"
|
|
650
|
+
|
|
651
|
+
# Collect required scripts from installed commands using YAML parser
|
|
652
|
+
REQUIRED_SCRIPTS=()
|
|
653
|
+
for cmd in "${INSTALLED_COMMANDS[@]}"; do
|
|
654
|
+
# Find the command index in the array
|
|
655
|
+
cmd_index=0
|
|
656
|
+
while true; do
|
|
657
|
+
cmd_name=$(yaml_get_nested "$TEMP_DIR/package.yaml" "contents.commands[$cmd_index].name" 2>/dev/null || echo "")
|
|
658
|
+
if [ -z "$cmd_name" ] || [ "$cmd_name" = "null" ]; then
|
|
659
|
+
break
|
|
660
|
+
fi
|
|
661
|
+
|
|
662
|
+
if [ "$cmd_name" = "$cmd" ]; then
|
|
663
|
+
# Found the command, now get its scripts
|
|
664
|
+
script_index=0
|
|
665
|
+
while true; do
|
|
666
|
+
script=$(yaml_get_nested "$TEMP_DIR/package.yaml" "contents.commands[$cmd_index].scripts[$script_index]" 2>/dev/null || echo "")
|
|
667
|
+
if [ -z "$script" ] || [ "$script" = "null" ]; then
|
|
668
|
+
break
|
|
669
|
+
fi
|
|
670
|
+
|
|
671
|
+
# Add to required scripts (with deduplication)
|
|
672
|
+
already_added=false
|
|
673
|
+
for existing in "${REQUIRED_SCRIPTS[@]}"; do
|
|
674
|
+
if [ "$existing" = "$script" ]; then
|
|
675
|
+
already_added=true
|
|
676
|
+
break
|
|
677
|
+
fi
|
|
678
|
+
done
|
|
679
|
+
|
|
680
|
+
if [ "$already_added" = false ]; then
|
|
681
|
+
REQUIRED_SCRIPTS+=("$script")
|
|
682
|
+
fi
|
|
683
|
+
|
|
684
|
+
script_index=$((script_index + 1))
|
|
685
|
+
done
|
|
686
|
+
break
|
|
687
|
+
fi
|
|
688
|
+
|
|
689
|
+
cmd_index=$((cmd_index + 1))
|
|
690
|
+
done
|
|
691
|
+
done
|
|
692
|
+
|
|
693
|
+
echo " Found ${#REQUIRED_SCRIPTS[@]} required script(s): ${REQUIRED_SCRIPTS[@]}"
|
|
694
|
+
|
|
695
|
+
# Install required scripts and add to ALL_FILES_TO_INSTALL for batch manifest update
|
|
696
|
+
SCRIPT_FILES_LIST=""
|
|
697
|
+
if [ ${#REQUIRED_SCRIPTS[@]} -gt 0 ]; then
|
|
698
|
+
mkdir -p "$INSTALL_BASE_DIR/scripts"
|
|
699
|
+
for script in "${REQUIRED_SCRIPTS[@]}"; do
|
|
700
|
+
script_path="$TEMP_DIR/agent/scripts/$script"
|
|
701
|
+
|
|
702
|
+
# Check if script exists
|
|
703
|
+
if [ ! -f "$script_path" ]; then
|
|
704
|
+
echo " ${RED}✗${NC} Script not found: $script (declared in package.yaml)"
|
|
705
|
+
continue
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
# Check if should install based on experimental status
|
|
709
|
+
if ! should_install_file "$script" "scripts"; then
|
|
710
|
+
continue
|
|
711
|
+
fi
|
|
712
|
+
|
|
713
|
+
# Copy script and make executable
|
|
714
|
+
cp "$script_path" "$INSTALL_BASE_DIR/scripts/$script"
|
|
715
|
+
chmod +x "$INSTALL_BASE_DIR/scripts/$script"
|
|
716
|
+
|
|
717
|
+
# Get file version and store metadata
|
|
718
|
+
FILE_VERSION=$(get_file_version "$TEMP_DIR/package.yaml" "scripts" "$script")
|
|
719
|
+
|
|
720
|
+
# Check experimental status
|
|
721
|
+
is_experimental=""
|
|
722
|
+
if [ -f "$TEMP_DIR/package.yaml" ]; then
|
|
723
|
+
is_experimental=$(grep -A 1000 "^ scripts:" "$TEMP_DIR/package.yaml" 2>/dev/null | grep -A 2 "name: ${script}" | grep "^ *experimental: true" | grep -v "^[[:space:]]*#" | head -1)
|
|
724
|
+
fi
|
|
725
|
+
FILE_METADATA["scripts/$script"]="$FILE_VERSION|$is_experimental"
|
|
726
|
+
|
|
727
|
+
# Track for batch processing
|
|
728
|
+
if [ -n "$SCRIPT_FILES_LIST" ]; then
|
|
729
|
+
SCRIPT_FILES_LIST="$SCRIPT_FILES_LIST $script_path"
|
|
730
|
+
else
|
|
731
|
+
SCRIPT_FILES_LIST="$script_path"
|
|
732
|
+
fi
|
|
733
|
+
done
|
|
734
|
+
fi
|
|
735
|
+
|
|
736
|
+
# Update ALL_FILES_TO_INSTALL with resolved scripts
|
|
737
|
+
if [ -n "$SCRIPT_FILES_LIST" ]; then
|
|
738
|
+
ALL_FILES_TO_INSTALL["scripts"]="$SCRIPT_FILES_LIST"
|
|
739
|
+
fi
|
|
740
|
+
echo ""
|
|
741
|
+
elif [ -d "$TEMP_DIR/agent/scripts" ] && [ -n "${ALL_FILES_TO_INSTALL[scripts]+x}" ]; then
|
|
742
|
+
# Scripts were collected during scan but no package.yaml script-command binding
|
|
743
|
+
# Install all scripts that passed validation (backward compatibility)
|
|
744
|
+
for file in ${ALL_FILES_TO_INSTALL[scripts]}; do
|
|
745
|
+
filename=$(basename "$file")
|
|
746
|
+
mkdir -p "$INSTALL_BASE_DIR/scripts"
|
|
747
|
+
cp "$file" "$INSTALL_BASE_DIR/scripts/$filename"
|
|
748
|
+
chmod +x "$INSTALL_BASE_DIR/scripts/$filename"
|
|
749
|
+
done
|
|
750
|
+
fi
|
|
751
|
+
|
|
752
|
+
# ============================================================================
|
|
753
|
+
# OPTIMIZATION: Batch checksum calculation
|
|
754
|
+
# ============================================================================
|
|
755
|
+
|
|
756
|
+
echo " ${BLUE}Calculating checksums...${NC}"
|
|
757
|
+
|
|
758
|
+
# Collect all installed files for batch checksum
|
|
759
|
+
ALL_INSTALLED_FILES=()
|
|
760
|
+
for dir in "${!ALL_FILES_TO_INSTALL[@]}"; do
|
|
761
|
+
SOURCE_DIR="$TEMP_DIR/agent/$dir"
|
|
762
|
+
for file in ${ALL_FILES_TO_INSTALL[$dir]}; do
|
|
763
|
+
if [ "$dir" = "files" ]; then
|
|
764
|
+
rel_path="${file#$SOURCE_DIR/}"
|
|
765
|
+
if [ "$HAS_FILE_METADATA" = true ]; then
|
|
766
|
+
_file_target="${FILE_TARGETS[files/$rel_path]:-./}"
|
|
767
|
+
_bname=$(basename "$rel_path")
|
|
768
|
+
_bname="${_bname%.template}"
|
|
769
|
+
ALL_INSTALLED_FILES+=("${_file_target}${_bname}")
|
|
770
|
+
else
|
|
771
|
+
ALL_INSTALLED_FILES+=("./$rel_path")
|
|
772
|
+
fi
|
|
773
|
+
else
|
|
774
|
+
filename=$(basename "$file")
|
|
775
|
+
ALL_INSTALLED_FILES+=("$INSTALL_BASE_DIR/$dir/$filename")
|
|
776
|
+
fi
|
|
777
|
+
done
|
|
778
|
+
done
|
|
779
|
+
|
|
780
|
+
# Calculate all checksums in one pass
|
|
781
|
+
declare -A CHECKSUMS
|
|
782
|
+
if [ ${#ALL_INSTALLED_FILES[@]} -gt 0 ]; then
|
|
783
|
+
while IFS= read -r line; do
|
|
784
|
+
checksum=$(echo "$line" | awk '{print $1}')
|
|
785
|
+
filepath=$(echo "$line" | awk '{$1=""; print substr($0,2)}')
|
|
786
|
+
CHECKSUMS["$filepath"]="$checksum"
|
|
787
|
+
done < <(if command -v sha256sum >/dev/null 2>&1; then sha256sum "${ALL_INSTALLED_FILES[@]}" 2>/dev/null; elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "${ALL_INSTALLED_FILES[@]}" 2>/dev/null; fi)
|
|
788
|
+
fi
|
|
789
|
+
|
|
790
|
+
# ============================================================================
|
|
791
|
+
# OPTIMIZATION: Batch manifest update
|
|
792
|
+
# ============================================================================
|
|
793
|
+
|
|
794
|
+
echo " ${BLUE}Updating manifest...${NC}"
|
|
795
|
+
|
|
796
|
+
# Parse manifest once
|
|
797
|
+
yaml_parse "$MANIFEST_FILE"
|
|
798
|
+
|
|
799
|
+
# Add all files to manifest in memory
|
|
800
|
+
timestamp=$(get_timestamp)
|
|
801
|
+
for dir in "${!ALL_FILES_TO_INSTALL[@]}"; do
|
|
802
|
+
SOURCE_DIR="$TEMP_DIR/agent/$dir"
|
|
803
|
+
manifest_key="${MANIFEST_KEYS[$dir]:-$dir}"
|
|
804
|
+
|
|
805
|
+
for file in ${ALL_FILES_TO_INSTALL[$dir]}; do
|
|
806
|
+
# Determine filename and installed filepath based on dir type
|
|
807
|
+
if [ "$dir" = "files" ]; then
|
|
808
|
+
filename="${file#$SOURCE_DIR/}"
|
|
809
|
+
if [ "$HAS_FILE_METADATA" = true ]; then
|
|
810
|
+
_file_target="${FILE_TARGETS[files/$filename]:-./}"
|
|
811
|
+
_bname=$(basename "$filename")
|
|
812
|
+
_bname="${_bname%.template}"
|
|
813
|
+
filepath="${_file_target}${_bname}"
|
|
814
|
+
else
|
|
815
|
+
filepath="./$filename"
|
|
816
|
+
fi
|
|
817
|
+
else
|
|
818
|
+
filename=$(basename "$file")
|
|
819
|
+
filepath="$INSTALL_BASE_DIR/$dir/$filename"
|
|
820
|
+
fi
|
|
821
|
+
|
|
822
|
+
# Get metadata
|
|
823
|
+
IFS='|' read -r file_version is_experimental <<< "${FILE_METADATA[$dir/$filename]}"
|
|
824
|
+
|
|
825
|
+
# Get checksum
|
|
826
|
+
checksum="${CHECKSUMS[$filepath]:-unknown}"
|
|
827
|
+
|
|
828
|
+
# Append to manifest using mapped key
|
|
829
|
+
obj_node=$(yaml_array_append_object ".packages.${PACKAGE_NAME}.files.${manifest_key}")
|
|
830
|
+
yaml_object_set "$obj_node" "name" "$filename" >/dev/null
|
|
831
|
+
yaml_object_set "$obj_node" "version" "$file_version" >/dev/null
|
|
832
|
+
yaml_object_set "$obj_node" "installed_at" "$timestamp" >/dev/null
|
|
833
|
+
yaml_object_set "$obj_node" "modified" "false" >/dev/null
|
|
834
|
+
yaml_object_set "$obj_node" "checksum" "sha256:$checksum" >/dev/null
|
|
835
|
+
|
|
836
|
+
if [ -n "$is_experimental" ]; then
|
|
837
|
+
yaml_object_set "$obj_node" "experimental" "true" >/dev/null
|
|
838
|
+
fi
|
|
839
|
+
|
|
840
|
+
# For files with metadata: store target path and variables
|
|
841
|
+
if [ "$dir" = "files" ] && [ "$HAS_FILE_METADATA" = true ]; then
|
|
842
|
+
yaml_object_set "$obj_node" "target" "$filepath" >/dev/null
|
|
843
|
+
# Store variable values if this file had variables
|
|
844
|
+
_file_vars_manifest="${FILE_VARS[files/$filename]:-}"
|
|
845
|
+
if [ -n "$_file_vars_manifest" ]; then
|
|
846
|
+
# Create nested map node for variables
|
|
847
|
+
_vars_node=$(create_node "map" "variables" "" "$obj_node")
|
|
848
|
+
add_child "$obj_node" "$_vars_node"
|
|
849
|
+
IFS=',' read -ra _var_names <<< "$_file_vars_manifest"
|
|
850
|
+
for _vname in "${_var_names[@]}"; do
|
|
851
|
+
_vval="${COLLECTED_VARS[$_vname]:-}"
|
|
852
|
+
if [ -n "$_vval" ]; then
|
|
853
|
+
yaml_object_set "$_vars_node" "$_vname" "$_vval" >/dev/null
|
|
854
|
+
fi
|
|
855
|
+
done
|
|
856
|
+
fi
|
|
857
|
+
fi
|
|
858
|
+
|
|
859
|
+
if [ "$dir" = "scripts" ]; then
|
|
860
|
+
echo " ${GREEN}✓${NC} Installed $dir/$filename (v$file_version) [executable]"
|
|
861
|
+
elif [ "$dir" = "files" ]; then
|
|
862
|
+
echo " ${GREEN}✓${NC} Installed $filename → $filepath"
|
|
863
|
+
else
|
|
864
|
+
echo " ${GREEN}✓${NC} Installed $dir/$filename (v$file_version)"
|
|
865
|
+
fi
|
|
866
|
+
done
|
|
867
|
+
done
|
|
868
|
+
|
|
869
|
+
# Write manifest once at the end
|
|
870
|
+
yaml_write "$MANIFEST_FILE"
|
|
871
|
+
|
|
872
|
+
echo ""
|
|
873
|
+
|
|
874
|
+
# Success message
|
|
875
|
+
if [ "$GLOBAL_INSTALL" = true ]; then
|
|
876
|
+
echo "${GREEN}✅ Package installed globally!${NC}"
|
|
877
|
+
echo ""
|
|
878
|
+
echo "Location: $INSTALL_BASE_DIR"
|
|
879
|
+
echo "Manifest: $MANIFEST_FILE"
|
|
880
|
+
else
|
|
881
|
+
echo "${GREEN}✅ Installation complete!${NC}"
|
|
882
|
+
echo ""
|
|
883
|
+
echo "Installed $INSTALLED_COUNT file(s) from:"
|
|
884
|
+
echo " $REPO_URL"
|
|
885
|
+
echo ""
|
|
886
|
+
echo "Package: $PACKAGE_NAME ($PACKAGE_VERSION)"
|
|
887
|
+
echo "Manifest: agent/manifest.yaml updated"
|
|
888
|
+
fi
|
|
889
|
+
|
|
890
|
+
echo ""
|
|
891
|
+
echo "${YELLOW}⚠️ Security Reminder:${NC}"
|
|
892
|
+
echo "Review installed files before using them."
|
|
893
|
+
echo ""
|