@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.
Files changed (180) hide show
  1. package/README.md +68 -0
  2. package/agent/commands/acp.clarification-address.md +417 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +437 -0
  5. package/agent/commands/acp.clarifications-research.md +326 -0
  6. package/agent/commands/acp.command-create.md +432 -0
  7. package/agent/commands/acp.design-create.md +286 -0
  8. package/agent/commands/acp.design-reference.md +355 -0
  9. package/agent/commands/acp.handoff.md +270 -0
  10. package/agent/commands/acp.index.md +423 -0
  11. package/agent/commands/acp.init.md +546 -0
  12. package/agent/commands/acp.package-create.md +895 -0
  13. package/agent/commands/acp.package-info.md +212 -0
  14. package/agent/commands/acp.package-install.md +539 -0
  15. package/agent/commands/acp.package-list.md +280 -0
  16. package/agent/commands/acp.package-publish.md +541 -0
  17. package/agent/commands/acp.package-remove.md +293 -0
  18. package/agent/commands/acp.package-search.md +307 -0
  19. package/agent/commands/acp.package-update.md +361 -0
  20. package/agent/commands/acp.package-validate.md +540 -0
  21. package/agent/commands/acp.pattern-create.md +386 -0
  22. package/agent/commands/acp.plan.md +587 -0
  23. package/agent/commands/acp.proceed.md +882 -0
  24. package/agent/commands/acp.project-create.md +675 -0
  25. package/agent/commands/acp.project-info.md +312 -0
  26. package/agent/commands/acp.project-list.md +226 -0
  27. package/agent/commands/acp.project-remove.md +379 -0
  28. package/agent/commands/acp.project-set.md +227 -0
  29. package/agent/commands/acp.project-update.md +307 -0
  30. package/agent/commands/acp.projects-restore.md +228 -0
  31. package/agent/commands/acp.projects-sync.md +347 -0
  32. package/agent/commands/acp.report.md +407 -0
  33. package/agent/commands/acp.resume.md +239 -0
  34. package/agent/commands/acp.sessions.md +301 -0
  35. package/agent/commands/acp.status.md +293 -0
  36. package/agent/commands/acp.sync.md +364 -0
  37. package/agent/commands/acp.task-create.md +500 -0
  38. package/agent/commands/acp.update.md +302 -0
  39. package/agent/commands/acp.validate.md +466 -0
  40. package/agent/commands/acp.version-check-for-updates.md +276 -0
  41. package/agent/commands/acp.version-check.md +191 -0
  42. package/agent/commands/acp.version-update.md +289 -0
  43. package/agent/commands/command.template.md +339 -0
  44. package/agent/commands/git.commit.md +526 -0
  45. package/agent/commands/git.init.md +514 -0
  46. package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
  47. package/agent/commands/tanstack-cloudflare.tail.md +275 -0
  48. package/agent/design/.gitkeep +0 -0
  49. package/agent/design/design.template.md +154 -0
  50. package/agent/design/local.dashboard-layout-routing.md +288 -0
  51. package/agent/design/local.data-model-yaml-parsing.md +310 -0
  52. package/agent/design/local.search-filtering.md +331 -0
  53. package/agent/design/local.server-api-auto-refresh.md +235 -0
  54. package/agent/design/local.table-tree-views.md +299 -0
  55. package/agent/design/local.visualizer-requirements.md +349 -0
  56. package/agent/design/requirements.template.md +387 -0
  57. package/agent/index/.gitkeep +0 -0
  58. package/agent/index/acp.core.yaml +137 -0
  59. package/agent/index/local.main.template.yaml +37 -0
  60. package/agent/manifest.template.yaml +13 -0
  61. package/agent/manifest.yaml +302 -0
  62. package/agent/milestones/.gitkeep +0 -0
  63. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
  64. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  65. package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
  66. package/agent/package.template.yaml +86 -0
  67. package/agent/patterns/.gitkeep +0 -0
  68. package/agent/patterns/bootstrap.template.md +1237 -0
  69. package/agent/patterns/pattern.template.md +382 -0
  70. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
  71. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
  72. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
  73. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
  74. package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
  75. package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
  76. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
  77. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
  78. package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
  79. package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
  80. package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
  81. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
  82. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
  83. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
  84. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
  85. package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
  86. package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
  87. package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
  88. package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
  89. package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
  90. package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
  91. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
  92. package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
  93. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
  94. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
  95. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
  96. package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
  97. package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
  98. package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
  99. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
  100. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
  101. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
  102. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
  103. package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
  104. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
  105. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
  106. package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
  107. package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
  108. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
  109. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
  110. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
  111. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
  112. package/agent/progress.template.yaml +161 -0
  113. package/agent/progress.yaml +145 -0
  114. package/agent/schemas/package.schema.yaml +276 -0
  115. package/agent/scripts/acp.common.sh +1781 -0
  116. package/agent/scripts/acp.install.sh +333 -0
  117. package/agent/scripts/acp.package-create.sh +924 -0
  118. package/agent/scripts/acp.package-info.sh +288 -0
  119. package/agent/scripts/acp.package-install.sh +893 -0
  120. package/agent/scripts/acp.package-list.sh +311 -0
  121. package/agent/scripts/acp.package-publish.sh +420 -0
  122. package/agent/scripts/acp.package-remove.sh +348 -0
  123. package/agent/scripts/acp.package-search.sh +156 -0
  124. package/agent/scripts/acp.package-update.sh +517 -0
  125. package/agent/scripts/acp.package-validate.sh +1018 -0
  126. package/agent/scripts/acp.uninstall.sh +85 -0
  127. package/agent/scripts/acp.version-check-for-updates.sh +98 -0
  128. package/agent/scripts/acp.version-check.sh +47 -0
  129. package/agent/scripts/acp.version-update.sh +176 -0
  130. package/agent/scripts/acp.yaml-parser.sh +985 -0
  131. package/agent/scripts/acp.yaml-validate.sh +205 -0
  132. package/agent/tasks/.gitkeep +0 -0
  133. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
  134. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
  135. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
  136. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
  137. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
  138. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
  139. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
  140. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
  141. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
  142. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
  143. package/agent/tasks/task-1-{title}.template.md +244 -0
  144. package/bin/visualize.mjs +84 -0
  145. package/package.json +48 -0
  146. package/src/components/ExtraFieldsBadge.tsx +15 -0
  147. package/src/components/FilterBar.tsx +33 -0
  148. package/src/components/Header.tsx +23 -0
  149. package/src/components/MilestoneTable.tsx +167 -0
  150. package/src/components/MilestoneTree.tsx +84 -0
  151. package/src/components/ProgressBar.tsx +20 -0
  152. package/src/components/SearchInput.tsx +22 -0
  153. package/src/components/Sidebar.tsx +54 -0
  154. package/src/components/StatusBadge.tsx +23 -0
  155. package/src/components/StatusDot.tsx +12 -0
  156. package/src/components/TaskList.tsx +36 -0
  157. package/src/components/ViewToggle.tsx +31 -0
  158. package/src/lib/config.ts +8 -0
  159. package/src/lib/file-watcher.ts +43 -0
  160. package/src/lib/search.ts +48 -0
  161. package/src/lib/types.ts +73 -0
  162. package/src/lib/useAutoRefresh.ts +31 -0
  163. package/src/lib/useCollapse.ts +31 -0
  164. package/src/lib/useFilteredData.ts +55 -0
  165. package/src/lib/yaml-loader-real.spec.ts +47 -0
  166. package/src/lib/yaml-loader.spec.ts +201 -0
  167. package/src/lib/yaml-loader.ts +265 -0
  168. package/src/routeTree.gen.ts +140 -0
  169. package/src/router.tsx +10 -0
  170. package/src/routes/__root.tsx +75 -0
  171. package/src/routes/api/watch.ts +29 -0
  172. package/src/routes/index.tsx +115 -0
  173. package/src/routes/milestones.tsx +50 -0
  174. package/src/routes/search.tsx +84 -0
  175. package/src/routes/tasks.tsx +63 -0
  176. package/src/services/progress-database.service.ts +46 -0
  177. package/src/styles.css +25 -0
  178. package/tsconfig.json +24 -0
  179. package/vite.config.ts +16 -0
  180. 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 ""