@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,1781 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Common utilities for ACP scripts
|
|
3
|
+
# POSIX-compliant for maximum portability
|
|
4
|
+
|
|
5
|
+
# Portable in-place sed (works on both GNU and BSD/macOS sed)
|
|
6
|
+
# Usage: _sed_i "expression" "file"
|
|
7
|
+
_sed_i() {
|
|
8
|
+
if [ "$(uname)" = "Darwin" ]; then
|
|
9
|
+
sed -i '' "$@"
|
|
10
|
+
else
|
|
11
|
+
sed -i "$@"
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Initialize colors using tput (more reliable than ANSI codes)
|
|
16
|
+
init_colors() {
|
|
17
|
+
if command -v tput >/dev/null 2>&1 && [ -t 1 ]; then
|
|
18
|
+
RED=$(tput setaf 1)
|
|
19
|
+
GREEN=$(tput setaf 2)
|
|
20
|
+
YELLOW=$(tput setaf 3)
|
|
21
|
+
BLUE=$(tput setaf 4)
|
|
22
|
+
BOLD=$(tput bold)
|
|
23
|
+
NC=$(tput sgr0)
|
|
24
|
+
else
|
|
25
|
+
RED=''
|
|
26
|
+
GREEN=''
|
|
27
|
+
YELLOW=''
|
|
28
|
+
BLUE=''
|
|
29
|
+
BOLD=''
|
|
30
|
+
NC=''
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Calculate file checksum (SHA-256)
|
|
35
|
+
# Usage: calculate_checksum "path/to/file"
|
|
36
|
+
# Returns: checksum string (without "sha256:" prefix)
|
|
37
|
+
calculate_checksum() {
|
|
38
|
+
local file="$1"
|
|
39
|
+
if [ ! -f "$file" ]; then
|
|
40
|
+
echo "Error: File not found: $file" >&2
|
|
41
|
+
return 1
|
|
42
|
+
fi
|
|
43
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
44
|
+
sha256sum "$file" 2>/dev/null | cut -d' ' -f1
|
|
45
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
46
|
+
shasum -a 256 "$file" 2>/dev/null | cut -d' ' -f1
|
|
47
|
+
else
|
|
48
|
+
echo "unknown"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Get current timestamp in ISO 8601 format (UTC)
|
|
53
|
+
# Usage: timestamp=$(get_timestamp)
|
|
54
|
+
# Returns: YYYY-MM-DDTHH:MM:SSZ
|
|
55
|
+
get_timestamp() {
|
|
56
|
+
date -u +"%Y-%m-%dT%H:%M:%SZ"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Validate URL format
|
|
60
|
+
# Usage: if validate_url "$url"; then ...
|
|
61
|
+
# Returns: 0 if valid, 1 if invalid
|
|
62
|
+
validate_url() {
|
|
63
|
+
local url="$1"
|
|
64
|
+
case "$url" in
|
|
65
|
+
http://*|https://*)
|
|
66
|
+
return 0
|
|
67
|
+
;;
|
|
68
|
+
*)
|
|
69
|
+
return 1
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Get script directory (portable way)
|
|
75
|
+
# Usage: script_dir=$(get_script_dir)
|
|
76
|
+
get_script_dir() {
|
|
77
|
+
# Get the directory of the calling script
|
|
78
|
+
dirname "$0"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Source YAML parser
|
|
82
|
+
# Usage: source_yaml_parser
|
|
83
|
+
source_yaml_parser() {
|
|
84
|
+
# Check if already loaded (don't re-source to preserve AST_FILE)
|
|
85
|
+
if [ -n "${YAML_PARSER_LOADED:-}" ]; then
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Try to find acp.yaml-parser.sh in multiple locations
|
|
90
|
+
local parser_locations=(
|
|
91
|
+
"$(dirname "${BASH_SOURCE[0]}")/acp.yaml-parser.sh"
|
|
92
|
+
"agent/scripts/acp.yaml-parser.sh"
|
|
93
|
+
"./agent/scripts/acp.yaml-parser.sh"
|
|
94
|
+
"../agent/scripts/acp.yaml-parser.sh"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
for parser_path in "${parser_locations[@]}"; do
|
|
98
|
+
if [ -f "$parser_path" ]; then
|
|
99
|
+
. "$parser_path"
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
echo "${RED}Error: acp.yaml-parser.sh not found${NC}" >&2
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Initialize manifest file if it doesn't exist
|
|
109
|
+
# Usage: init_manifest
|
|
110
|
+
init_manifest() {
|
|
111
|
+
if [ ! -f "agent/manifest.yaml" ]; then
|
|
112
|
+
cat > agent/manifest.yaml << 'EOF'
|
|
113
|
+
# ACP Package Manifest
|
|
114
|
+
# Tracks installed packages and their versions
|
|
115
|
+
|
|
116
|
+
packages: {}
|
|
117
|
+
|
|
118
|
+
manifest_version: 1.0.0
|
|
119
|
+
last_updated: null
|
|
120
|
+
EOF
|
|
121
|
+
echo "${GREEN}✓${NC} Created agent/manifest.yaml"
|
|
122
|
+
fi
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Validate manifest structure
|
|
126
|
+
# Usage: if validate_manifest; then ...
|
|
127
|
+
# Returns: 0 if valid, 1 if invalid
|
|
128
|
+
validate_manifest() {
|
|
129
|
+
local manifest="agent/manifest.yaml"
|
|
130
|
+
|
|
131
|
+
if [ ! -f "$manifest" ]; then
|
|
132
|
+
echo "${RED}Error: Manifest not found${NC}" >&2
|
|
133
|
+
return 1
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# Source YAML parser if not already loaded
|
|
137
|
+
if ! command -v yaml_get >/dev/null 2>&1; then
|
|
138
|
+
source_yaml_parser || return 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Check required fields
|
|
142
|
+
local manifest_version
|
|
143
|
+
manifest_version=$(yaml_get "$manifest" "manifest_version" 2>/dev/null)
|
|
144
|
+
|
|
145
|
+
if [ -z "$manifest_version" ] || [ "$manifest_version" = "null" ]; then
|
|
146
|
+
echo "${RED}Error: manifest_version missing${NC}" >&2
|
|
147
|
+
return 1
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
echo "${GREEN}✓${NC} Manifest valid"
|
|
151
|
+
return 0
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Update manifest last_updated timestamp
|
|
155
|
+
# Usage: update_manifest_timestamp
|
|
156
|
+
update_manifest_timestamp() {
|
|
157
|
+
local manifest="agent/manifest.yaml"
|
|
158
|
+
local timestamp
|
|
159
|
+
timestamp=$(get_timestamp)
|
|
160
|
+
|
|
161
|
+
# Update timestamp using sed
|
|
162
|
+
_sed_i "s/^last_updated: .*/last_updated: $timestamp/" "$manifest"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Check if package exists in manifest
|
|
166
|
+
# Usage: if package_exists "package-name" ["manifest-path"]; then ...
|
|
167
|
+
# Returns: 0 if exists, 1 if not
|
|
168
|
+
package_exists() {
|
|
169
|
+
local package_name="$1"
|
|
170
|
+
local manifest="${2:-agent/manifest.yaml}"
|
|
171
|
+
|
|
172
|
+
# Source YAML parser if not already loaded
|
|
173
|
+
if ! command -v yaml_has_key >/dev/null 2>&1; then
|
|
174
|
+
source_yaml_parser || return 1
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
yaml_has_key "$manifest" "packages.${package_name}.source"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# ============================================================================
|
|
181
|
+
# Global Manifest Functions
|
|
182
|
+
# ============================================================================
|
|
183
|
+
|
|
184
|
+
# Get global manifest path
|
|
185
|
+
# Usage: manifest_path=$(get_global_manifest_path)
|
|
186
|
+
# Returns: Path to global manifest
|
|
187
|
+
get_global_manifest_path() {
|
|
188
|
+
echo "$HOME/.acp/agent/manifest.yaml"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Check if global manifest exists
|
|
192
|
+
# Usage: if global_manifest_exists; then ...
|
|
193
|
+
# Returns: 0 if exists, 1 if not
|
|
194
|
+
global_manifest_exists() {
|
|
195
|
+
local manifest_path
|
|
196
|
+
manifest_path=$(get_global_manifest_path)
|
|
197
|
+
[ -f "$manifest_path" ]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Initialize global manifest if it doesn't exist
|
|
201
|
+
# Usage: init_global_manifest
|
|
202
|
+
init_global_manifest() {
|
|
203
|
+
local manifest_path
|
|
204
|
+
manifest_path=$(get_global_manifest_path)
|
|
205
|
+
|
|
206
|
+
if [ -f "$manifest_path" ]; then
|
|
207
|
+
return 0
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Create ~/.acp directory if needed
|
|
211
|
+
mkdir -p "$HOME/.acp/projects"
|
|
212
|
+
|
|
213
|
+
# Create manifest
|
|
214
|
+
local timestamp
|
|
215
|
+
timestamp=$(get_timestamp)
|
|
216
|
+
|
|
217
|
+
cat > "$manifest_path" << EOF
|
|
218
|
+
# Global ACP Package Manifest
|
|
219
|
+
# This file tracks all globally installed ACP packages
|
|
220
|
+
|
|
221
|
+
version: 1.0.0
|
|
222
|
+
updated: $timestamp
|
|
223
|
+
|
|
224
|
+
packages: {}
|
|
225
|
+
EOF
|
|
226
|
+
|
|
227
|
+
success "Initialized global manifest at $manifest_path"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Read global manifest (returns full content)
|
|
231
|
+
# Usage: content=$(read_global_manifest)
|
|
232
|
+
read_global_manifest() {
|
|
233
|
+
local manifest_path
|
|
234
|
+
manifest_path=$(get_global_manifest_path)
|
|
235
|
+
|
|
236
|
+
if [ ! -f "$manifest_path" ]; then
|
|
237
|
+
echo "Error: Global manifest not found at $manifest_path" >&2
|
|
238
|
+
return 1
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
cat "$manifest_path"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# Update global manifest timestamp
|
|
245
|
+
# Usage: update_global_manifest_timestamp
|
|
246
|
+
update_global_manifest_timestamp() {
|
|
247
|
+
local manifest_path
|
|
248
|
+
manifest_path=$(get_global_manifest_path)
|
|
249
|
+
|
|
250
|
+
if [ ! -f "$manifest_path" ]; then
|
|
251
|
+
echo "Error: Global manifest not found" >&2
|
|
252
|
+
return 1
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# Update timestamp using sed
|
|
256
|
+
local timestamp
|
|
257
|
+
timestamp=$(get_timestamp)
|
|
258
|
+
_sed_i "s/^updated: .*/updated: $timestamp/" "$manifest_path"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Check if package exists in global manifest
|
|
262
|
+
# Usage: if global_package_exists "package-name"; then ...
|
|
263
|
+
# Returns: 0 if exists, 1 if not
|
|
264
|
+
global_package_exists() {
|
|
265
|
+
local package_name="$1"
|
|
266
|
+
local manifest_path
|
|
267
|
+
manifest_path=$(get_global_manifest_path)
|
|
268
|
+
|
|
269
|
+
if [ ! -f "$manifest_path" ]; then
|
|
270
|
+
return 1
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# Check if package exists in manifest
|
|
274
|
+
grep -q "^ $package_name:" "$manifest_path"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Get global package location
|
|
278
|
+
# Usage: location=$(get_global_package_location "package-name")
|
|
279
|
+
# Returns: Package installation path
|
|
280
|
+
get_global_package_location() {
|
|
281
|
+
local package_name="$1"
|
|
282
|
+
local manifest_path
|
|
283
|
+
manifest_path=$(get_global_manifest_path)
|
|
284
|
+
|
|
285
|
+
if [ ! -f "$manifest_path" ]; then
|
|
286
|
+
return 1
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
# Extract location using awk
|
|
290
|
+
awk -v pkg="$package_name" '
|
|
291
|
+
$0 ~ "^ " pkg ":" { in_package=1; next }
|
|
292
|
+
in_package && /^ location:/ { print $2; exit }
|
|
293
|
+
/^ [a-z]/ && in_package { exit }
|
|
294
|
+
' "$manifest_path"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Initialize global ACP infrastructure if it doesn't exist
|
|
298
|
+
# This function is idempotent - safe to call multiple times
|
|
299
|
+
# Usage: init_global_acp
|
|
300
|
+
# Returns: 0 on success, 1 on failure
|
|
301
|
+
init_global_acp() {
|
|
302
|
+
local global_dir="$HOME/.acp"
|
|
303
|
+
|
|
304
|
+
# Check if already initialized
|
|
305
|
+
if [ -d "$global_dir/agent" ] && [ -f "$global_dir/AGENT.md" ]; then
|
|
306
|
+
return 0 # Already initialized, nothing to do
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
echo "${BLUE}Initializing global ACP infrastructure at ~/.acp/...${NC}"
|
|
310
|
+
echo ""
|
|
311
|
+
|
|
312
|
+
# Create ~/.acp directory
|
|
313
|
+
mkdir -p "$global_dir"
|
|
314
|
+
|
|
315
|
+
# Create .gitignore for global ACP directory
|
|
316
|
+
if [ ! -f "$global_dir/.gitignore" ]; then
|
|
317
|
+
cat > "$global_dir/.gitignore" << 'GITIGNORE'
|
|
318
|
+
# Project repos have their own git
|
|
319
|
+
projects/
|
|
320
|
+
|
|
321
|
+
# Claude Code session data
|
|
322
|
+
.claude/
|
|
323
|
+
|
|
324
|
+
# Common noise
|
|
325
|
+
*.log
|
|
326
|
+
node_modules/
|
|
327
|
+
GITIGNORE
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# Get the directory where this script is located
|
|
331
|
+
local script_dir
|
|
332
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
333
|
+
|
|
334
|
+
# Run standard ACP installation in ~/.acp/
|
|
335
|
+
# This installs all templates, scripts, and schemas
|
|
336
|
+
if [ -f "$script_dir/acp.install.sh" ]; then
|
|
337
|
+
# Use local install script
|
|
338
|
+
(
|
|
339
|
+
cd "$global_dir" || exit 1
|
|
340
|
+
bash "$script_dir/acp.install.sh"
|
|
341
|
+
) || {
|
|
342
|
+
echo "${RED}Error: Failed to initialize global ACP infrastructure${NC}" >&2
|
|
343
|
+
return 1
|
|
344
|
+
}
|
|
345
|
+
else
|
|
346
|
+
# Fallback: Download from repository
|
|
347
|
+
(
|
|
348
|
+
cd "$global_dir" || exit 1
|
|
349
|
+
curl -fsSL https://raw.githubusercontent.com/prmichaelsen/agent-context-protocol/mainline/agent/scripts/acp.install.sh | bash
|
|
350
|
+
) || {
|
|
351
|
+
echo "${RED}Error: Failed to initialize global ACP infrastructure${NC}" >&2
|
|
352
|
+
return 1
|
|
353
|
+
}
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
# Create additional global directories
|
|
357
|
+
mkdir -p "$global_dir/projects"
|
|
358
|
+
|
|
359
|
+
# Initialize global manifest if it doesn't exist
|
|
360
|
+
if [ ! -f "$global_dir/agent/manifest.yaml" ]; then
|
|
361
|
+
init_global_manifest
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
# Initialize projects registry
|
|
365
|
+
if [ ! -f "$HOME/.acp/projects.yaml" ]; then
|
|
366
|
+
init_projects_registry
|
|
367
|
+
echo "${GREEN}✓${NC} Initialized projects registry"
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
# Append global installation notes to AGENT.md
|
|
371
|
+
if [ -f "$global_dir/AGENT.md" ] && ! grep -q "## Global Installation" "$global_dir/AGENT.md"; then
|
|
372
|
+
cat >> "$global_dir/AGENT.md" << 'EOF'
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Global Installation
|
|
377
|
+
|
|
378
|
+
This is a global ACP installation located at `~/.acp/`.
|
|
379
|
+
|
|
380
|
+
### Purpose
|
|
381
|
+
|
|
382
|
+
This installation provides:
|
|
383
|
+
- **Global packages** in `~/.acp/agent/` - Packages installed with `@acp.package-install --global`
|
|
384
|
+
- **Project workspace** in `~/.acp/projects/` - Optional location for package development
|
|
385
|
+
- **Global manifest** in `~/.acp/agent/manifest.yaml` - Tracks globally installed packages
|
|
386
|
+
- **Templates and scripts** in `~/.acp/agent/` - All ACP templates and utilities
|
|
387
|
+
|
|
388
|
+
### Usage
|
|
389
|
+
|
|
390
|
+
**Install packages globally**:
|
|
391
|
+
```bash
|
|
392
|
+
@acp.package-install --global https://github.com/user/acp-package.git
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Create packages**:
|
|
396
|
+
```bash
|
|
397
|
+
cd ~/.acp/projects
|
|
398
|
+
@acp.package-create
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**List global packages**:
|
|
402
|
+
```bash
|
|
403
|
+
@acp.package-list --global
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Discovery
|
|
407
|
+
|
|
408
|
+
Agents can discover globally installed packages by reading `~/.acp/agent/manifest.yaml`. Local packages always take precedence over global packages.
|
|
409
|
+
EOF
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
echo ""
|
|
413
|
+
echo "${GREEN}✓ Global ACP infrastructure initialized${NC}"
|
|
414
|
+
echo ""
|
|
415
|
+
echo "Location: $global_dir"
|
|
416
|
+
echo "Templates: $global_dir/agent/"
|
|
417
|
+
echo "Projects: $global_dir/projects/"
|
|
418
|
+
echo ""
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# Print error message and exit
|
|
422
|
+
# Usage: die "Error message"
|
|
423
|
+
die() {
|
|
424
|
+
echo "${RED}Error: $1${NC}" >&2
|
|
425
|
+
exit 1
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# Print warning message
|
|
429
|
+
# Usage: warn "Warning message"
|
|
430
|
+
warn() {
|
|
431
|
+
echo "${YELLOW}Warning: $1${NC}" >&2
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# Print success message
|
|
435
|
+
# Usage: success "Success message"
|
|
436
|
+
success() {
|
|
437
|
+
echo "${GREEN}✓${NC} $1"
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
# Print info message
|
|
441
|
+
# Usage: info "Info message"
|
|
442
|
+
info() {
|
|
443
|
+
echo "${BLUE}ℹ${NC} $1"
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# Remove deprecated script files (from versions < 2.0.0)
|
|
447
|
+
# Usage: cleanup_deprecated_scripts
|
|
448
|
+
cleanup_deprecated_scripts() {
|
|
449
|
+
local deprecated_scripts=(
|
|
450
|
+
"check-for-updates.sh"
|
|
451
|
+
"common.sh"
|
|
452
|
+
"install.sh"
|
|
453
|
+
"package-install.sh"
|
|
454
|
+
"uninstall.sh"
|
|
455
|
+
"update.sh"
|
|
456
|
+
"version.sh"
|
|
457
|
+
"yaml.sh"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
local removed_count=0
|
|
461
|
+
for script in "${deprecated_scripts[@]}"; do
|
|
462
|
+
if [ -f "agent/scripts/$script" ]; then
|
|
463
|
+
rm "agent/scripts/$script"
|
|
464
|
+
warn "Removed deprecated script: $script"
|
|
465
|
+
removed_count=$((removed_count + 1))
|
|
466
|
+
fi
|
|
467
|
+
done
|
|
468
|
+
|
|
469
|
+
if [ $removed_count -gt 0 ]; then
|
|
470
|
+
success "Cleaned up $removed_count deprecated script(s)"
|
|
471
|
+
fi
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
# Parse package.yaml from repository
|
|
475
|
+
# Usage: parse_package_metadata "repo_dir"
|
|
476
|
+
# Sets global variables: PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_DESCRIPTION
|
|
477
|
+
parse_package_metadata() {
|
|
478
|
+
local repo_dir="$1"
|
|
479
|
+
local package_yaml="${repo_dir}/package.yaml"
|
|
480
|
+
|
|
481
|
+
if [ ! -f "$package_yaml" ]; then
|
|
482
|
+
warn "package.yaml not found in repository"
|
|
483
|
+
PACKAGE_NAME="unknown"
|
|
484
|
+
PACKAGE_VERSION="0.0.0"
|
|
485
|
+
PACKAGE_DESCRIPTION="No description"
|
|
486
|
+
return 1
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
# Source YAML parser if not already loaded
|
|
490
|
+
if ! command -v yaml_get >/dev/null 2>&1; then
|
|
491
|
+
source_yaml_parser || return 1
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
# Extract metadata
|
|
495
|
+
PACKAGE_NAME=$(yaml_get "$package_yaml" "name" 2>/dev/null || echo "unknown")
|
|
496
|
+
PACKAGE_VERSION=$(yaml_get "$package_yaml" "version" 2>/dev/null || echo "0.0.0")
|
|
497
|
+
PACKAGE_DESCRIPTION=$(yaml_get "$package_yaml" "description" 2>/dev/null || echo "No description")
|
|
498
|
+
|
|
499
|
+
info "Package: $PACKAGE_NAME"
|
|
500
|
+
info "Version: $PACKAGE_VERSION"
|
|
501
|
+
info "Description: $PACKAGE_DESCRIPTION"
|
|
502
|
+
|
|
503
|
+
return 0
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
# Get file version from package.yaml
|
|
507
|
+
# Usage: get_file_version "package.yaml" "patterns" "filename.md"
|
|
508
|
+
# Returns: version string or "0.0.0" if not found
|
|
509
|
+
get_file_version() {
|
|
510
|
+
local package_yaml="$1"
|
|
511
|
+
local file_type="$2"
|
|
512
|
+
local file_name="$3"
|
|
513
|
+
|
|
514
|
+
if [ ! -f "$package_yaml" ]; then
|
|
515
|
+
echo "0.0.0"
|
|
516
|
+
return 0
|
|
517
|
+
fi
|
|
518
|
+
|
|
519
|
+
# Use awk to parse YAML array (acp.yaml.sh doesn't support array queries)
|
|
520
|
+
local version
|
|
521
|
+
version=$(awk -v type="$file_type" -v name="$file_name" '
|
|
522
|
+
BEGIN { in_section=0; in_item=0 }
|
|
523
|
+
/^ [a-z_]+:/ { in_section=0 }
|
|
524
|
+
$0 ~ "^ " type ":" { in_section=1; next }
|
|
525
|
+
in_section && /^ - name:/ {
|
|
526
|
+
if ($3 == name) { in_item=1 }
|
|
527
|
+
else { in_item=0 }
|
|
528
|
+
next
|
|
529
|
+
}
|
|
530
|
+
in_section && in_item && /^ version:/ {
|
|
531
|
+
print $2
|
|
532
|
+
exit
|
|
533
|
+
}
|
|
534
|
+
' "$package_yaml")
|
|
535
|
+
|
|
536
|
+
if [ -z "$version" ]; then
|
|
537
|
+
echo "0.0.0"
|
|
538
|
+
else
|
|
539
|
+
echo "$version"
|
|
540
|
+
fi
|
|
541
|
+
|
|
542
|
+
return 0
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# Add package to manifest
|
|
546
|
+
# Usage: add_package_to_manifest "package_name" "source_url" "version" "commit_hash"
|
|
547
|
+
add_package_to_manifest() {
|
|
548
|
+
local package_name="$1"
|
|
549
|
+
local source_url="$2"
|
|
550
|
+
local package_version="$3"
|
|
551
|
+
local commit_hash="$4"
|
|
552
|
+
local timestamp
|
|
553
|
+
timestamp=$(get_timestamp)
|
|
554
|
+
|
|
555
|
+
local manifest="agent/manifest.yaml"
|
|
556
|
+
|
|
557
|
+
# Add package metadata using direct YAML appending (new parser doesn't support yaml_set for new keys)
|
|
558
|
+
# Check if package already exists
|
|
559
|
+
if grep -q "^ ${package_name}:" "$manifest" 2>/dev/null; then
|
|
560
|
+
# Update existing package
|
|
561
|
+
_sed_i "/^ ${package_name}:/,/^ [a-z]/ {
|
|
562
|
+
s|source: .*|source: $source_url|
|
|
563
|
+
s|package_version: .*|package_version: $package_version|
|
|
564
|
+
s|commit: .*|commit: $commit_hash|
|
|
565
|
+
s|updated_at: .*|updated_at: $timestamp|
|
|
566
|
+
}" "$manifest"
|
|
567
|
+
else
|
|
568
|
+
# Add new package entry
|
|
569
|
+
# Find the packages: line and append after it
|
|
570
|
+
awk -v pkg="$package_name" -v src="$source_url" -v ver="$package_version" -v commit="$commit_hash" -v ts="$timestamp" '
|
|
571
|
+
/^packages:/ {
|
|
572
|
+
if ($2 == "{}") {
|
|
573
|
+
# Empty packages - replace {} with just "packages:"
|
|
574
|
+
print "packages:"
|
|
575
|
+
} else {
|
|
576
|
+
print
|
|
577
|
+
}
|
|
578
|
+
print " " pkg ":"
|
|
579
|
+
print " source: " src
|
|
580
|
+
print " package_version: " ver
|
|
581
|
+
print " commit: " commit
|
|
582
|
+
print " installed_at: " ts
|
|
583
|
+
print " updated_at: " ts
|
|
584
|
+
print " files:"
|
|
585
|
+
print " patterns: []"
|
|
586
|
+
print " commands: []"
|
|
587
|
+
print " designs: []"
|
|
588
|
+
print " scripts: []"
|
|
589
|
+
print " files: []"
|
|
590
|
+
print " indices: []"
|
|
591
|
+
next
|
|
592
|
+
}
|
|
593
|
+
{ print }
|
|
594
|
+
' "$manifest" > "$manifest.tmp" && mv "$manifest.tmp" "$manifest"
|
|
595
|
+
fi
|
|
596
|
+
|
|
597
|
+
# Update manifest timestamp
|
|
598
|
+
update_manifest_timestamp
|
|
599
|
+
|
|
600
|
+
success "Added package $package_name to manifest"
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
# Add file to manifest
|
|
604
|
+
# Usage: add_file_to_manifest "package_name" "file_type" "filename" "version" "file_path"
|
|
605
|
+
# file_type: patterns, commands, designs
|
|
606
|
+
add_file_to_manifest() {
|
|
607
|
+
local package_name="$1"
|
|
608
|
+
local file_type="$2"
|
|
609
|
+
local filename="$3"
|
|
610
|
+
local file_version="$4"
|
|
611
|
+
local file_path="$5"
|
|
612
|
+
local package_yaml_path="$6" # Optional: path to package.yaml for experimental checking
|
|
613
|
+
local timestamp
|
|
614
|
+
timestamp=$(get_timestamp)
|
|
615
|
+
|
|
616
|
+
local manifest="agent/manifest.yaml"
|
|
617
|
+
|
|
618
|
+
# Calculate checksum
|
|
619
|
+
local checksum
|
|
620
|
+
checksum=$(calculate_checksum "$file_path")
|
|
621
|
+
|
|
622
|
+
if [ $? -ne 0 ]; then
|
|
623
|
+
warn "Failed to calculate checksum for $filename"
|
|
624
|
+
checksum="unknown"
|
|
625
|
+
fi
|
|
626
|
+
|
|
627
|
+
# Check if experimental (if package.yaml provided)
|
|
628
|
+
local is_experimental=""
|
|
629
|
+
if [ -n "$package_yaml_path" ] && [ -f "$package_yaml_path" ]; then
|
|
630
|
+
is_experimental=$(grep -A 1000 "^ ${file_type}:" "$package_yaml_path" 2>/dev/null | grep -A 2 "name: ${filename}" | grep "^ *experimental: true" | grep -v "^[[:space:]]*#" | head -1)
|
|
631
|
+
fi
|
|
632
|
+
|
|
633
|
+
# Source YAML parser
|
|
634
|
+
if ! command -v yaml_parse >/dev/null 2>&1; then
|
|
635
|
+
source_yaml_parser || return 1
|
|
636
|
+
fi
|
|
637
|
+
|
|
638
|
+
# Convert empty arrays [] to proper format first (workaround for parser limitation)
|
|
639
|
+
_sed_i "s/^ ${file_type}: \\[\\]$/ ${file_type}:/" "$manifest"
|
|
640
|
+
|
|
641
|
+
# Parse manifest
|
|
642
|
+
yaml_parse "$manifest"
|
|
643
|
+
|
|
644
|
+
# Append object to array
|
|
645
|
+
local obj_node
|
|
646
|
+
obj_node=$(yaml_array_append_object ".packages.${package_name}.files.${file_type}")
|
|
647
|
+
|
|
648
|
+
# Set object fields
|
|
649
|
+
yaml_object_set "$obj_node" "name" "$filename" >/dev/null
|
|
650
|
+
yaml_object_set "$obj_node" "version" "$file_version" >/dev/null
|
|
651
|
+
yaml_object_set "$obj_node" "installed_at" "$timestamp" >/dev/null
|
|
652
|
+
yaml_object_set "$obj_node" "modified" "false" >/dev/null
|
|
653
|
+
yaml_object_set "$obj_node" "checksum" "sha256:$checksum" >/dev/null
|
|
654
|
+
|
|
655
|
+
# Add experimental field if marked
|
|
656
|
+
if [ -n "$is_experimental" ]; then
|
|
657
|
+
yaml_object_set "$obj_node" "experimental" "true" >/dev/null
|
|
658
|
+
fi
|
|
659
|
+
|
|
660
|
+
# Write back
|
|
661
|
+
yaml_write "$manifest"
|
|
662
|
+
|
|
663
|
+
return 0
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
# Get package commit hash from git repository
|
|
667
|
+
# Usage: get_commit_hash "repo_dir"
|
|
668
|
+
# Returns: commit hash
|
|
669
|
+
get_commit_hash() {
|
|
670
|
+
local repo_dir="$1"
|
|
671
|
+
|
|
672
|
+
if [ ! -d "$repo_dir/.git" ]; then
|
|
673
|
+
echo "unknown"
|
|
674
|
+
return 0
|
|
675
|
+
fi
|
|
676
|
+
|
|
677
|
+
(cd "$repo_dir" && git rev-parse HEAD 2>/dev/null) || echo "unknown"
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Compare semantic versions
|
|
681
|
+
# Usage: compare_versions "1.2.3" "1.3.0"
|
|
682
|
+
# Returns: "newer" if remote > current, "same" if equal, "older" if remote < current
|
|
683
|
+
compare_versions() {
|
|
684
|
+
local current="$1"
|
|
685
|
+
local remote="$2"
|
|
686
|
+
|
|
687
|
+
if [ "$current" = "$remote" ]; then
|
|
688
|
+
echo "same"
|
|
689
|
+
return 0
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
# Use sort -V for version comparison
|
|
693
|
+
local older
|
|
694
|
+
older=$(printf '%s\n%s\n' "$current" "$remote" | sort -V | head -n1)
|
|
695
|
+
|
|
696
|
+
if [ "$older" = "$current" ]; then
|
|
697
|
+
echo "newer"
|
|
698
|
+
else
|
|
699
|
+
echo "older"
|
|
700
|
+
fi
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
# Check if file was modified locally
|
|
704
|
+
# Usage: is_file_modified "package_name" "file_type" "filename"
|
|
705
|
+
# Returns: 0 if modified, 1 if not modified
|
|
706
|
+
is_file_modified() {
|
|
707
|
+
local package_name="$1"
|
|
708
|
+
local file_type="$2"
|
|
709
|
+
local file_name="$3"
|
|
710
|
+
local manifest="agent/manifest.yaml"
|
|
711
|
+
|
|
712
|
+
# Get stored checksum from manifest
|
|
713
|
+
local stored_checksum
|
|
714
|
+
stored_checksum=$(awk -v pkg="$package_name" -v type="$file_type" -v name="$file_name" '
|
|
715
|
+
BEGIN { in_pkg=0; in_type=0; in_file=0 }
|
|
716
|
+
$0 ~ "^ " pkg ":" { in_pkg=1; next }
|
|
717
|
+
in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
|
|
718
|
+
in_pkg && $0 ~ "^ " type ":" { in_type=1; next }
|
|
719
|
+
in_type && /^ [a-z]/ && !/^ / { in_type=0 }
|
|
720
|
+
in_type && /^ - name:/ {
|
|
721
|
+
if ($3 == name) { in_file=1 }
|
|
722
|
+
else { in_file=0 }
|
|
723
|
+
next
|
|
724
|
+
}
|
|
725
|
+
in_file && /^ checksum:/ {
|
|
726
|
+
gsub(/sha256:/, "", $2)
|
|
727
|
+
print $2
|
|
728
|
+
exit
|
|
729
|
+
}
|
|
730
|
+
' "$manifest")
|
|
731
|
+
|
|
732
|
+
if [ -z "$stored_checksum" ]; then
|
|
733
|
+
warn "No checksum found in manifest for $file_type/$file_name"
|
|
734
|
+
return 1
|
|
735
|
+
fi
|
|
736
|
+
|
|
737
|
+
# Calculate current checksum
|
|
738
|
+
# Map manifest key to filesystem directory (they differ for some types)
|
|
739
|
+
local file_dir="$file_type"
|
|
740
|
+
case "$file_type" in
|
|
741
|
+
indices) file_dir="index" ;;
|
|
742
|
+
esac
|
|
743
|
+
local current_checksum
|
|
744
|
+
current_checksum=$(calculate_checksum "agent/${file_dir}/${file_name}")
|
|
745
|
+
|
|
746
|
+
if [ "$stored_checksum" != "$current_checksum" ]; then
|
|
747
|
+
return 0 # Modified
|
|
748
|
+
else
|
|
749
|
+
return 1 # Not modified
|
|
750
|
+
fi
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
# Update file entry in manifest
|
|
754
|
+
# Usage: update_file_in_manifest "package_name" "file_type" "filename" "new_version" "new_checksum"
|
|
755
|
+
update_file_in_manifest() {
|
|
756
|
+
local package_name="$1"
|
|
757
|
+
local file_type="$2"
|
|
758
|
+
local file_name="$3"
|
|
759
|
+
local new_version="$4"
|
|
760
|
+
local new_checksum="$5"
|
|
761
|
+
local timestamp
|
|
762
|
+
timestamp=$(get_timestamp)
|
|
763
|
+
|
|
764
|
+
local manifest="agent/manifest.yaml"
|
|
765
|
+
|
|
766
|
+
# Update using awk to modify in place
|
|
767
|
+
# This is complex with acp.yaml.sh, so we'll use a temp file approach
|
|
768
|
+
local temp_file
|
|
769
|
+
temp_file=$(mktemp)
|
|
770
|
+
|
|
771
|
+
awk -v pkg="$package_name" -v type="$file_type" -v name="$file_name" \
|
|
772
|
+
-v ver="$new_version" -v chk="sha256:$new_checksum" -v ts="$timestamp" '
|
|
773
|
+
BEGIN { in_pkg=0; in_type=0; in_file=0 }
|
|
774
|
+
$0 ~ "^ " pkg ":" { in_pkg=1; print; next }
|
|
775
|
+
in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0; print; next }
|
|
776
|
+
in_pkg && $0 ~ "^ " type ":" { in_type=1; print; next }
|
|
777
|
+
in_type && /^ [a-z]/ && !/^ / { in_type=0; print; next }
|
|
778
|
+
in_type && /^ - name:/ {
|
|
779
|
+
if ($3 == name) { in_file=1 }
|
|
780
|
+
else { in_file=0 }
|
|
781
|
+
print
|
|
782
|
+
next
|
|
783
|
+
}
|
|
784
|
+
in_file && /^ version:/ {
|
|
785
|
+
print " version: " ver
|
|
786
|
+
next
|
|
787
|
+
}
|
|
788
|
+
in_file && /^ checksum:/ {
|
|
789
|
+
print " checksum: " chk
|
|
790
|
+
next
|
|
791
|
+
}
|
|
792
|
+
in_file && /^ modified:/ {
|
|
793
|
+
print " modified: false"
|
|
794
|
+
next
|
|
795
|
+
}
|
|
796
|
+
{ print }
|
|
797
|
+
' "$manifest" > "$temp_file"
|
|
798
|
+
|
|
799
|
+
mv "$temp_file" "$manifest"
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
# ============================================================================
|
|
803
|
+
# Template File Manifest Functions
|
|
804
|
+
# ============================================================================
|
|
805
|
+
|
|
806
|
+
# Check if a template file was modified locally (uses target path, not agent/ path)
|
|
807
|
+
# Usage: is_template_file_modified "package_name" "filename" "target_path"
|
|
808
|
+
# Returns: 0 if modified, 1 if not modified
|
|
809
|
+
is_template_file_modified() {
|
|
810
|
+
local package_name="$1"
|
|
811
|
+
local file_name="$2"
|
|
812
|
+
local target_path="$3"
|
|
813
|
+
local manifest="agent/manifest.yaml"
|
|
814
|
+
|
|
815
|
+
# Get stored checksum from manifest
|
|
816
|
+
local stored_checksum
|
|
817
|
+
stored_checksum=$(awk -v pkg="$package_name" -v name="$file_name" '
|
|
818
|
+
BEGIN { in_pkg=0; in_files=0; in_file=0 }
|
|
819
|
+
$0 ~ "^ " pkg ":" { in_pkg=1; next }
|
|
820
|
+
in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
|
|
821
|
+
in_pkg && /^ files:/ { in_files=1; next }
|
|
822
|
+
in_files && /^ [a-z]/ && !/^ / { in_files=0 }
|
|
823
|
+
in_files && /^ - name:/ {
|
|
824
|
+
if ($3 == name) { in_file=1 }
|
|
825
|
+
else { in_file=0 }
|
|
826
|
+
next
|
|
827
|
+
}
|
|
828
|
+
in_file && /^ checksum:/ {
|
|
829
|
+
gsub(/sha256:/, "", $2)
|
|
830
|
+
print $2
|
|
831
|
+
exit
|
|
832
|
+
}
|
|
833
|
+
' "$manifest")
|
|
834
|
+
|
|
835
|
+
if [ -z "$stored_checksum" ]; then
|
|
836
|
+
warn "No checksum found in manifest for files/$file_name"
|
|
837
|
+
return 1
|
|
838
|
+
fi
|
|
839
|
+
|
|
840
|
+
# Calculate current checksum from target path
|
|
841
|
+
if [ ! -f "$target_path" ]; then
|
|
842
|
+
warn "Target file not found: $target_path"
|
|
843
|
+
return 0 # Missing = modified (deleted)
|
|
844
|
+
fi
|
|
845
|
+
|
|
846
|
+
local current_checksum
|
|
847
|
+
current_checksum=$(calculate_checksum "$target_path")
|
|
848
|
+
|
|
849
|
+
if [ "$stored_checksum" != "$current_checksum" ]; then
|
|
850
|
+
return 0 # Modified
|
|
851
|
+
else
|
|
852
|
+
return 1 # Not modified
|
|
853
|
+
fi
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
# Get target path for a template file from manifest
|
|
857
|
+
# Usage: target=$(get_template_file_target "package_name" "filename")
|
|
858
|
+
get_template_file_target() {
|
|
859
|
+
local package_name="$1"
|
|
860
|
+
local file_name="$2"
|
|
861
|
+
local manifest="agent/manifest.yaml"
|
|
862
|
+
|
|
863
|
+
awk -v pkg="$package_name" -v name="$file_name" '
|
|
864
|
+
BEGIN { in_pkg=0; in_files=0; in_file=0 }
|
|
865
|
+
$0 ~ "^ " pkg ":" { in_pkg=1; next }
|
|
866
|
+
in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
|
|
867
|
+
in_pkg && /^ files:/ { in_files=1; next }
|
|
868
|
+
in_files && /^ [a-z]/ && !/^ / { in_files=0 }
|
|
869
|
+
in_files && /^ - name:/ {
|
|
870
|
+
if ($3 == name) { in_file=1 }
|
|
871
|
+
else { in_file=0 }
|
|
872
|
+
next
|
|
873
|
+
}
|
|
874
|
+
in_file && /^ target:/ {
|
|
875
|
+
$1=""
|
|
876
|
+
gsub(/^ +/, "")
|
|
877
|
+
print
|
|
878
|
+
exit
|
|
879
|
+
}
|
|
880
|
+
' "$manifest"
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
# Get stored variable values for a template file from manifest
|
|
884
|
+
# Usage: vars=$(get_template_file_variables "package_name" "filename")
|
|
885
|
+
# Returns: KEY=VALUE lines (one per line)
|
|
886
|
+
get_template_file_variables() {
|
|
887
|
+
local package_name="$1"
|
|
888
|
+
local file_name="$2"
|
|
889
|
+
local manifest="agent/manifest.yaml"
|
|
890
|
+
|
|
891
|
+
awk -v pkg="$package_name" -v name="$file_name" '
|
|
892
|
+
BEGIN { in_pkg=0; in_files=0; in_file=0; in_vars=0 }
|
|
893
|
+
$0 ~ "^ " pkg ":" { in_pkg=1; next }
|
|
894
|
+
in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0 }
|
|
895
|
+
in_pkg && /^ files:/ { in_files=1; next }
|
|
896
|
+
in_files && /^ [a-z]/ && !/^ / { in_files=0 }
|
|
897
|
+
in_files && /^ - name:/ {
|
|
898
|
+
if ($3 == name) { in_file=1 }
|
|
899
|
+
else { in_file=0; in_vars=0 }
|
|
900
|
+
next
|
|
901
|
+
}
|
|
902
|
+
in_file && /^ variables:/ { in_vars=1; next }
|
|
903
|
+
in_vars && /^ [A-Z]/ {
|
|
904
|
+
key=$1
|
|
905
|
+
gsub(/:$/, "", key)
|
|
906
|
+
$1=""
|
|
907
|
+
gsub(/^ +/, "")
|
|
908
|
+
print key "=" $0
|
|
909
|
+
next
|
|
910
|
+
}
|
|
911
|
+
in_vars && /^ [a-z]/ { in_vars=0 }
|
|
912
|
+
in_vars && /^ -/ { in_vars=0; in_file=0 }
|
|
913
|
+
' "$manifest"
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
# Update template file entry in manifest
|
|
917
|
+
# Usage: update_template_file_in_manifest "package_name" "filename" "new_version" "new_checksum"
|
|
918
|
+
update_template_file_in_manifest() {
|
|
919
|
+
local package_name="$1"
|
|
920
|
+
local file_name="$2"
|
|
921
|
+
local new_version="$3"
|
|
922
|
+
local new_checksum="$4"
|
|
923
|
+
local timestamp
|
|
924
|
+
timestamp=$(get_timestamp)
|
|
925
|
+
|
|
926
|
+
local manifest="agent/manifest.yaml"
|
|
927
|
+
|
|
928
|
+
local temp_file
|
|
929
|
+
temp_file=$(mktemp)
|
|
930
|
+
|
|
931
|
+
awk -v pkg="$package_name" -v name="$file_name" \
|
|
932
|
+
-v ver="$new_version" -v chk="sha256:$new_checksum" -v ts="$timestamp" '
|
|
933
|
+
BEGIN { in_pkg=0; in_files=0; in_file=0 }
|
|
934
|
+
$0 ~ "^ " pkg ":" { in_pkg=1; print; next }
|
|
935
|
+
in_pkg && /^ [a-z]/ && !/^ / { in_pkg=0; print; next }
|
|
936
|
+
in_pkg && /^ files:/ { in_files=1; print; next }
|
|
937
|
+
in_files && /^ [a-z]/ && !/^ / { in_files=0; print; next }
|
|
938
|
+
in_files && /^ - name:/ {
|
|
939
|
+
if ($3 == name) { in_file=1 }
|
|
940
|
+
else { in_file=0 }
|
|
941
|
+
print
|
|
942
|
+
next
|
|
943
|
+
}
|
|
944
|
+
in_file && /^ version:/ {
|
|
945
|
+
print " version: " ver
|
|
946
|
+
next
|
|
947
|
+
}
|
|
948
|
+
in_file && /^ checksum:/ {
|
|
949
|
+
print " checksum: " chk
|
|
950
|
+
next
|
|
951
|
+
}
|
|
952
|
+
in_file && /^ modified:/ {
|
|
953
|
+
print " modified: false"
|
|
954
|
+
next
|
|
955
|
+
}
|
|
956
|
+
{ print }
|
|
957
|
+
' "$manifest" > "$temp_file"
|
|
958
|
+
|
|
959
|
+
mv "$temp_file" "$manifest"
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
# ============================================================================
|
|
963
|
+
# Dependency Checking Functions
|
|
964
|
+
# ============================================================================
|
|
965
|
+
|
|
966
|
+
# Detect project package manager
|
|
967
|
+
# Usage: detect_package_manager
|
|
968
|
+
# Returns: npm, pip, cargo, go, or unknown
|
|
969
|
+
detect_package_manager() {
|
|
970
|
+
if [ -f "package.json" ]; then
|
|
971
|
+
echo "npm"
|
|
972
|
+
elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
|
|
973
|
+
echo "pip"
|
|
974
|
+
elif [ -f "Cargo.toml" ]; then
|
|
975
|
+
echo "cargo"
|
|
976
|
+
elif [ -f "go.mod" ]; then
|
|
977
|
+
echo "go"
|
|
978
|
+
else
|
|
979
|
+
echo "unknown"
|
|
980
|
+
fi
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
# Check npm dependency
|
|
984
|
+
# Usage: check_npm_dependency "dep_name" "required_version"
|
|
985
|
+
# Returns: installed version or "not-installed"
|
|
986
|
+
check_npm_dependency() {
|
|
987
|
+
local dep_name="$1"
|
|
988
|
+
local required_version="$2"
|
|
989
|
+
|
|
990
|
+
if [ ! -f "package.json" ]; then
|
|
991
|
+
echo "not-installed"
|
|
992
|
+
return 1
|
|
993
|
+
fi
|
|
994
|
+
|
|
995
|
+
# Check if jq is available
|
|
996
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
997
|
+
warn "jq not found, skipping npm dependency check"
|
|
998
|
+
echo "unknown"
|
|
999
|
+
return 0
|
|
1000
|
+
fi
|
|
1001
|
+
|
|
1002
|
+
# Get installed version
|
|
1003
|
+
local installed_version
|
|
1004
|
+
installed_version=$(jq -r ".dependencies.\"${dep_name}\" // .devDependencies.\"${dep_name}\" // \"not-installed\"" package.json 2>/dev/null)
|
|
1005
|
+
|
|
1006
|
+
if [ "$installed_version" = "not-installed" ] || [ "$installed_version" = "null" ]; then
|
|
1007
|
+
echo "not-installed"
|
|
1008
|
+
return 1
|
|
1009
|
+
fi
|
|
1010
|
+
|
|
1011
|
+
# Remove ^ ~ >= etc for display
|
|
1012
|
+
installed_version=$(echo "$installed_version" | sed 's/[\^~>=<]//g')
|
|
1013
|
+
|
|
1014
|
+
echo "$installed_version"
|
|
1015
|
+
return 0
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
# Check pip dependency
|
|
1019
|
+
# Usage: check_pip_dependency "dep_name" "required_version"
|
|
1020
|
+
# Returns: installed version or "not-installed"
|
|
1021
|
+
check_pip_dependency() {
|
|
1022
|
+
local dep_name="$1"
|
|
1023
|
+
local required_version="$2"
|
|
1024
|
+
|
|
1025
|
+
# Check requirements.txt
|
|
1026
|
+
if [ -f "requirements.txt" ]; then
|
|
1027
|
+
local version
|
|
1028
|
+
version=$(grep "^${dep_name}" requirements.txt 2>/dev/null | cut -d'=' -f2 | head -n1)
|
|
1029
|
+
if [ -n "$version" ]; then
|
|
1030
|
+
echo "$version"
|
|
1031
|
+
return 0
|
|
1032
|
+
fi
|
|
1033
|
+
fi
|
|
1034
|
+
|
|
1035
|
+
# Check pyproject.toml
|
|
1036
|
+
if [ -f "pyproject.toml" ]; then
|
|
1037
|
+
local version
|
|
1038
|
+
version=$(grep "${dep_name}" pyproject.toml 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -n1)
|
|
1039
|
+
if [ -n "$version" ]; then
|
|
1040
|
+
echo "$version"
|
|
1041
|
+
return 0
|
|
1042
|
+
fi
|
|
1043
|
+
fi
|
|
1044
|
+
|
|
1045
|
+
echo "not-installed"
|
|
1046
|
+
return 1
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
# Check cargo dependency
|
|
1050
|
+
# Usage: check_cargo_dependency "dep_name" "required_version"
|
|
1051
|
+
# Returns: installed version or "not-installed"
|
|
1052
|
+
check_cargo_dependency() {
|
|
1053
|
+
local dep_name="$1"
|
|
1054
|
+
local required_version="$2"
|
|
1055
|
+
|
|
1056
|
+
if [ ! -f "Cargo.toml" ]; then
|
|
1057
|
+
echo "not-installed"
|
|
1058
|
+
return 1
|
|
1059
|
+
fi
|
|
1060
|
+
|
|
1061
|
+
local version
|
|
1062
|
+
version=$(grep "^${dep_name}" Cargo.toml 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -n1)
|
|
1063
|
+
|
|
1064
|
+
if [ -n "$version" ]; then
|
|
1065
|
+
echo "$version"
|
|
1066
|
+
return 0
|
|
1067
|
+
fi
|
|
1068
|
+
|
|
1069
|
+
echo "not-installed"
|
|
1070
|
+
return 1
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
# Check go dependency
|
|
1074
|
+
# Usage: check_go_dependency "dep_name" "required_version"
|
|
1075
|
+
# Returns: installed version or "not-installed"
|
|
1076
|
+
check_go_dependency() {
|
|
1077
|
+
local dep_name="$1"
|
|
1078
|
+
local required_version="$2"
|
|
1079
|
+
|
|
1080
|
+
if [ ! -f "go.mod" ]; then
|
|
1081
|
+
echo "not-installed"
|
|
1082
|
+
return 1
|
|
1083
|
+
fi
|
|
1084
|
+
|
|
1085
|
+
local version
|
|
1086
|
+
version=$(grep "${dep_name}" go.mod 2>/dev/null | grep -oP 'v\d+\.\d+\.\d+' | sed 's/^v//' | head -n1)
|
|
1087
|
+
|
|
1088
|
+
if [ -n "$version" ]; then
|
|
1089
|
+
echo "$version"
|
|
1090
|
+
return 0
|
|
1091
|
+
fi
|
|
1092
|
+
|
|
1093
|
+
echo "not-installed"
|
|
1094
|
+
return 1
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
# Validate project dependencies
|
|
1098
|
+
# Usage: validate_project_dependencies "package_yaml_path"
|
|
1099
|
+
# Returns: 0 if valid or user confirms, 1 if invalid and user cancels
|
|
1100
|
+
validate_project_dependencies() {
|
|
1101
|
+
local package_yaml="$1"
|
|
1102
|
+
local package_manager
|
|
1103
|
+
package_manager=$(detect_package_manager)
|
|
1104
|
+
|
|
1105
|
+
if [ "$package_manager" = "unknown" ]; then
|
|
1106
|
+
info "No package manager detected, skipping dependency check"
|
|
1107
|
+
return 0
|
|
1108
|
+
fi
|
|
1109
|
+
|
|
1110
|
+
echo ""
|
|
1111
|
+
echo "${BLUE}Checking project dependencies ($package_manager)...${NC}"
|
|
1112
|
+
echo ""
|
|
1113
|
+
|
|
1114
|
+
# Source YAML parser if not already loaded
|
|
1115
|
+
if ! command -v yaml_get >/dev/null 2>&1; then
|
|
1116
|
+
source_yaml_parser || return 1
|
|
1117
|
+
fi
|
|
1118
|
+
|
|
1119
|
+
# Check if requires section exists
|
|
1120
|
+
local has_requires
|
|
1121
|
+
has_requires=$(grep -c "^requires:" "$package_yaml" 2>/dev/null || echo "0")
|
|
1122
|
+
|
|
1123
|
+
if [ "$has_requires" -eq 0 ]; then
|
|
1124
|
+
success "No project dependencies required"
|
|
1125
|
+
return 0
|
|
1126
|
+
fi
|
|
1127
|
+
|
|
1128
|
+
# Check if package manager section exists
|
|
1129
|
+
local has_pm_section
|
|
1130
|
+
has_pm_section=$(grep -c "^ ${package_manager}:" "$package_yaml" 2>/dev/null || echo "0")
|
|
1131
|
+
|
|
1132
|
+
if [ "$has_pm_section" -eq 0 ]; then
|
|
1133
|
+
success "No ${package_manager} dependencies required"
|
|
1134
|
+
return 0
|
|
1135
|
+
fi
|
|
1136
|
+
|
|
1137
|
+
local has_incompatible=false
|
|
1138
|
+
local dep_count=0
|
|
1139
|
+
|
|
1140
|
+
# Parse dependencies using awk
|
|
1141
|
+
while IFS=: read -r dep_name required_version; do
|
|
1142
|
+
# Skip empty lines and section headers
|
|
1143
|
+
[ -z "$dep_name" ] && continue
|
|
1144
|
+
[[ "$dep_name" =~ ^[[:space:]]*$ ]] && continue
|
|
1145
|
+
[[ "$dep_name" =~ ^requires ]] && continue
|
|
1146
|
+
[[ "$dep_name" =~ ^[[:space:]]*${package_manager} ]] && continue
|
|
1147
|
+
|
|
1148
|
+
# Clean up whitespace
|
|
1149
|
+
dep_name=$(echo "$dep_name" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
1150
|
+
required_version=$(echo "$required_version" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' | tr -d '"' | tr -d "'")
|
|
1151
|
+
|
|
1152
|
+
# Skip if not a dependency line
|
|
1153
|
+
[[ ! "$dep_name" =~ ^[a-zA-Z0-9@/_-]+$ ]] && continue
|
|
1154
|
+
|
|
1155
|
+
dep_count=$((dep_count + 1))
|
|
1156
|
+
|
|
1157
|
+
# Check if installed
|
|
1158
|
+
local installed_version=""
|
|
1159
|
+
case $package_manager in
|
|
1160
|
+
npm)
|
|
1161
|
+
installed_version=$(check_npm_dependency "$dep_name" "$required_version")
|
|
1162
|
+
;;
|
|
1163
|
+
pip)
|
|
1164
|
+
installed_version=$(check_pip_dependency "$dep_name" "$required_version")
|
|
1165
|
+
;;
|
|
1166
|
+
cargo)
|
|
1167
|
+
installed_version=$(check_cargo_dependency "$dep_name" "$required_version")
|
|
1168
|
+
;;
|
|
1169
|
+
go)
|
|
1170
|
+
installed_version=$(check_go_dependency "$dep_name" "$required_version")
|
|
1171
|
+
;;
|
|
1172
|
+
esac
|
|
1173
|
+
|
|
1174
|
+
if [ "$installed_version" = "not-installed" ]; then
|
|
1175
|
+
echo " ${RED}✗${NC} $dep_name: not installed (requires $required_version)"
|
|
1176
|
+
has_incompatible=true
|
|
1177
|
+
elif [ "$installed_version" = "unknown" ]; then
|
|
1178
|
+
echo " ${YELLOW}?${NC} $dep_name: unable to verify (requires $required_version)"
|
|
1179
|
+
else
|
|
1180
|
+
echo " ${GREEN}✓${NC} $dep_name: $installed_version (requires $required_version)"
|
|
1181
|
+
fi
|
|
1182
|
+
done < <(awk -v pm="$package_manager" '
|
|
1183
|
+
BEGIN { in_requires=0; in_pm=0 }
|
|
1184
|
+
/^requires:/ { in_requires=1; next }
|
|
1185
|
+
in_requires && /^[a-z]/ && !/^ / { in_requires=0 }
|
|
1186
|
+
in_requires && $0 ~ "^ " pm ":" { in_pm=1; next }
|
|
1187
|
+
in_pm && /^ [a-z]/ && !/^ / { in_pm=0 }
|
|
1188
|
+
in_pm && /^ [a-zA-Z0-9@/_-]+:/ {
|
|
1189
|
+
print $0
|
|
1190
|
+
}
|
|
1191
|
+
' "$package_yaml")
|
|
1192
|
+
|
|
1193
|
+
echo ""
|
|
1194
|
+
|
|
1195
|
+
if [ "$dep_count" -eq 0 ]; then
|
|
1196
|
+
success "No ${package_manager} dependencies required"
|
|
1197
|
+
return 0
|
|
1198
|
+
fi
|
|
1199
|
+
|
|
1200
|
+
if [ "$has_incompatible" = true ]; then
|
|
1201
|
+
echo "${YELLOW}⚠️ Some dependencies are missing or incompatible${NC}"
|
|
1202
|
+
echo ""
|
|
1203
|
+
echo "Recommendation:"
|
|
1204
|
+
echo " Install missing dependencies before using this package"
|
|
1205
|
+
echo " The package patterns may not work correctly without them"
|
|
1206
|
+
echo ""
|
|
1207
|
+
|
|
1208
|
+
# Only prompt if not in auto-confirm mode
|
|
1209
|
+
if [ "${SKIP_CONFIRM:-false}" != "true" ]; then
|
|
1210
|
+
read -p "Continue installation anyway? (y/N) " -n 1 -r
|
|
1211
|
+
echo
|
|
1212
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
1213
|
+
return 1
|
|
1214
|
+
fi
|
|
1215
|
+
else
|
|
1216
|
+
warn "Auto-confirm enabled, continuing despite missing dependencies"
|
|
1217
|
+
fi
|
|
1218
|
+
else
|
|
1219
|
+
success "All dependencies satisfied"
|
|
1220
|
+
fi
|
|
1221
|
+
|
|
1222
|
+
return 0
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
# ============================================================================
|
|
1226
|
+
# Namespace Utilities
|
|
1227
|
+
# ============================================================================
|
|
1228
|
+
|
|
1229
|
+
# Check if current directory is an ACP package
|
|
1230
|
+
# Usage: if is_acp_package; then ...
|
|
1231
|
+
# Returns: 0 if package.yaml exists, 1 otherwise
|
|
1232
|
+
is_acp_package() {
|
|
1233
|
+
[ -f "package.yaml" ]
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
# Infer package namespace from multiple sources
|
|
1237
|
+
# Usage: namespace=$(infer_namespace)
|
|
1238
|
+
# Returns: namespace string or empty if can't infer
|
|
1239
|
+
# Priority: 1) package.yaml, 2) directory name, 3) git remote
|
|
1240
|
+
infer_namespace() {
|
|
1241
|
+
local namespace=""
|
|
1242
|
+
|
|
1243
|
+
# Priority 1: Read from package.yaml
|
|
1244
|
+
if [ -f "package.yaml" ]; then
|
|
1245
|
+
namespace=$(yaml_get "package.yaml" "name" 2>/dev/null)
|
|
1246
|
+
if [ -n "$namespace" ]; then
|
|
1247
|
+
echo "$namespace"
|
|
1248
|
+
return 0
|
|
1249
|
+
fi
|
|
1250
|
+
fi
|
|
1251
|
+
|
|
1252
|
+
# Priority 2: Parse from directory name (acp-{namespace})
|
|
1253
|
+
local dir_name=$(basename "$PWD")
|
|
1254
|
+
if [[ "$dir_name" =~ ^acp-(.+)$ ]]; then
|
|
1255
|
+
namespace="${BASH_REMATCH[1]}"
|
|
1256
|
+
echo "$namespace"
|
|
1257
|
+
return 0
|
|
1258
|
+
fi
|
|
1259
|
+
|
|
1260
|
+
# Priority 3: Parse from git remote URL
|
|
1261
|
+
if git remote get-url origin >/dev/null 2>&1; then
|
|
1262
|
+
local remote_url=$(git remote get-url origin)
|
|
1263
|
+
if [[ "$remote_url" =~ acp-([a-z0-9-]+)(\.git)?$ ]]; then
|
|
1264
|
+
namespace="${BASH_REMATCH[1]}"
|
|
1265
|
+
echo "$namespace"
|
|
1266
|
+
return 0
|
|
1267
|
+
fi
|
|
1268
|
+
fi
|
|
1269
|
+
|
|
1270
|
+
# Could not infer
|
|
1271
|
+
return 1
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
# Validate namespace format and check reserved names
|
|
1275
|
+
# Usage: if validate_namespace "firebase"; then ...
|
|
1276
|
+
# Returns: 0 if valid, 1 if invalid
|
|
1277
|
+
validate_namespace() {
|
|
1278
|
+
local namespace="$1"
|
|
1279
|
+
|
|
1280
|
+
if [ -z "$namespace" ]; then
|
|
1281
|
+
echo "${RED}Error: Namespace cannot be empty${NC}" >&2
|
|
1282
|
+
return 1
|
|
1283
|
+
fi
|
|
1284
|
+
|
|
1285
|
+
# Check format (lowercase, alphanumeric, hyphens)
|
|
1286
|
+
if ! echo "$namespace" | grep -qE '^[a-z0-9-]+$'; then
|
|
1287
|
+
echo "${RED}Error: Namespace must be lowercase, alphanumeric, and hyphens only${NC}" >&2
|
|
1288
|
+
return 1
|
|
1289
|
+
fi
|
|
1290
|
+
|
|
1291
|
+
# Check reserved names
|
|
1292
|
+
case "$namespace" in
|
|
1293
|
+
acp|local|core|system|global)
|
|
1294
|
+
echo "${RED}Error: Namespace '$namespace' is reserved${NC}" >&2
|
|
1295
|
+
return 1
|
|
1296
|
+
;;
|
|
1297
|
+
esac
|
|
1298
|
+
|
|
1299
|
+
return 0
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
# Get namespace for file creation (context-aware)
|
|
1303
|
+
# Usage: namespace=$(get_namespace_for_file)
|
|
1304
|
+
# Returns: package namespace or "local" for non-packages
|
|
1305
|
+
get_namespace_for_file() {
|
|
1306
|
+
if is_acp_package; then
|
|
1307
|
+
local namespace=$(infer_namespace)
|
|
1308
|
+
if [ -n "$namespace" ]; then
|
|
1309
|
+
echo "$namespace"
|
|
1310
|
+
return 0
|
|
1311
|
+
else
|
|
1312
|
+
# In package but can't infer, ask user
|
|
1313
|
+
read -p "Package namespace: " namespace
|
|
1314
|
+
if validate_namespace "$namespace"; then
|
|
1315
|
+
echo "$namespace"
|
|
1316
|
+
return 0
|
|
1317
|
+
else
|
|
1318
|
+
return 1
|
|
1319
|
+
fi
|
|
1320
|
+
fi
|
|
1321
|
+
else
|
|
1322
|
+
# Not a package, use local namespace
|
|
1323
|
+
echo "local"
|
|
1324
|
+
return 0
|
|
1325
|
+
fi
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
# Validate namespace consistency across sources
|
|
1329
|
+
# Usage: if validate_namespace_consistency; then ...
|
|
1330
|
+
# Returns: 0 if consistent, 1 if conflicts found
|
|
1331
|
+
validate_namespace_consistency() {
|
|
1332
|
+
if ! is_acp_package; then
|
|
1333
|
+
return 0 # Not a package, no consistency to check
|
|
1334
|
+
fi
|
|
1335
|
+
|
|
1336
|
+
local from_yaml=$(yaml_get "package.yaml" "name" 2>/dev/null)
|
|
1337
|
+
local from_dir=$(basename "$PWD" | sed 's/^acp-//')
|
|
1338
|
+
local from_remote=""
|
|
1339
|
+
|
|
1340
|
+
if git remote get-url origin >/dev/null 2>&1; then
|
|
1341
|
+
local remote_url=$(git remote get-url origin)
|
|
1342
|
+
if [[ "$remote_url" =~ acp-([a-z0-9-]+)(\.git)?$ ]]; then
|
|
1343
|
+
from_remote="${BASH_REMATCH[1]}"
|
|
1344
|
+
fi
|
|
1345
|
+
fi
|
|
1346
|
+
|
|
1347
|
+
# Check for conflicts
|
|
1348
|
+
local has_conflict=false
|
|
1349
|
+
|
|
1350
|
+
if [ -n "$from_yaml" ] && [ -n "$from_dir" ] && [ "$from_yaml" != "$from_dir" ]; then
|
|
1351
|
+
echo "${YELLOW}Warning: Namespace mismatch${NC}" >&2
|
|
1352
|
+
echo " package.yaml: $from_yaml" >&2
|
|
1353
|
+
echo " directory: $from_dir" >&2
|
|
1354
|
+
has_conflict=true
|
|
1355
|
+
fi
|
|
1356
|
+
|
|
1357
|
+
if [ -n "$from_yaml" ] && [ -n "$from_remote" ] && [ "$from_yaml" != "$from_remote" ]; then
|
|
1358
|
+
echo "${YELLOW}Warning: Namespace mismatch${NC}" >&2
|
|
1359
|
+
echo " package.yaml: $from_yaml" >&2
|
|
1360
|
+
echo " git remote: $from_remote" >&2
|
|
1361
|
+
has_conflict=true
|
|
1362
|
+
fi
|
|
1363
|
+
|
|
1364
|
+
if [ "$has_conflict" = true ]; then
|
|
1365
|
+
return 1
|
|
1366
|
+
fi
|
|
1367
|
+
|
|
1368
|
+
return 0
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
# ============================================================================
|
|
1372
|
+
# README Update Utilities
|
|
1373
|
+
# ============================================================================
|
|
1374
|
+
|
|
1375
|
+
# Update README.md contents section from package.yaml
|
|
1376
|
+
# Usage: update_readme_contents
|
|
1377
|
+
# Returns: 0 if successful, 1 if error
|
|
1378
|
+
update_readme_contents() {
|
|
1379
|
+
local readme="README.md"
|
|
1380
|
+
local package_yaml="package.yaml"
|
|
1381
|
+
|
|
1382
|
+
if [ ! -f "$readme" ]; then
|
|
1383
|
+
echo "${YELLOW}Warning: README.md not found${NC}" >&2
|
|
1384
|
+
return 1
|
|
1385
|
+
fi
|
|
1386
|
+
|
|
1387
|
+
if [ ! -f "$package_yaml" ]; then
|
|
1388
|
+
echo "${YELLOW}Warning: package.yaml not found${NC}" >&2
|
|
1389
|
+
return 1
|
|
1390
|
+
fi
|
|
1391
|
+
|
|
1392
|
+
# Generate contents section
|
|
1393
|
+
local contents=$(generate_contents_section)
|
|
1394
|
+
|
|
1395
|
+
# Check if markers exist
|
|
1396
|
+
if ! grep -q "<!-- ACP_AUTO_UPDATE_START:CONTENTS -->" "$readme"; then
|
|
1397
|
+
echo "${YELLOW}Warning: README.md missing auto-update markers${NC}" >&2
|
|
1398
|
+
return 1
|
|
1399
|
+
fi
|
|
1400
|
+
|
|
1401
|
+
# Replace section between markers using awk
|
|
1402
|
+
awk -v contents="$contents" '
|
|
1403
|
+
/<!-- ACP_AUTO_UPDATE_START:CONTENTS -->/ {
|
|
1404
|
+
print
|
|
1405
|
+
print contents
|
|
1406
|
+
skip=1
|
|
1407
|
+
next
|
|
1408
|
+
}
|
|
1409
|
+
/<!-- ACP_AUTO_UPDATE_END:CONTENTS -->/ {
|
|
1410
|
+
skip=0
|
|
1411
|
+
}
|
|
1412
|
+
!skip
|
|
1413
|
+
' "$readme" > "${readme}.tmp"
|
|
1414
|
+
|
|
1415
|
+
mv "${readme}.tmp" "$readme"
|
|
1416
|
+
echo "${GREEN}✓${NC} Updated README.md contents section"
|
|
1417
|
+
return 0
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
# Generate contents section from package.yaml
|
|
1421
|
+
# Usage: contents=$(generate_contents_section)
|
|
1422
|
+
# Returns: Formatted markdown content list
|
|
1423
|
+
generate_contents_section() {
|
|
1424
|
+
local package_yaml="package.yaml"
|
|
1425
|
+
|
|
1426
|
+
# Parse and format contents using awk
|
|
1427
|
+
awk '
|
|
1428
|
+
BEGIN { section="" }
|
|
1429
|
+
|
|
1430
|
+
/^ commands:/ { section="commands"; print "### Commands"; next }
|
|
1431
|
+
/^ patterns:/ { section="patterns"; print ""; print "### Patterns"; next }
|
|
1432
|
+
/^ designs:/ { section="designs"; print ""; print "### Designs"; next }
|
|
1433
|
+
|
|
1434
|
+
section != "" && /^ - name:/ {
|
|
1435
|
+
gsub(/^ - name: /, "")
|
|
1436
|
+
name = $0
|
|
1437
|
+
getline
|
|
1438
|
+
if (/^ version:/) {
|
|
1439
|
+
getline
|
|
1440
|
+
if (/^ description:/) {
|
|
1441
|
+
gsub(/^ description: /, "")
|
|
1442
|
+
desc = $0
|
|
1443
|
+
print "- `" name "` - " desc
|
|
1444
|
+
} else {
|
|
1445
|
+
print "- `" name "`"
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/^[a-z]/ && !/^ / { section="" }
|
|
1451
|
+
' "$package_yaml"
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
# Add file to README contents (updates entire section)
|
|
1455
|
+
# Usage: add_file_to_readme "patterns" "firebase.my-pattern.md" "Description"
|
|
1456
|
+
add_file_to_readme() {
|
|
1457
|
+
local type="$1"
|
|
1458
|
+
local filename="$2"
|
|
1459
|
+
local description="$3"
|
|
1460
|
+
|
|
1461
|
+
# Simply update entire contents section
|
|
1462
|
+
update_readme_contents
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
# ============================================================================
|
|
1466
|
+
# Display Functions
|
|
1467
|
+
# ============================================================================
|
|
1468
|
+
|
|
1469
|
+
# Display available ACP commands
|
|
1470
|
+
# Usage: display_available_commands
|
|
1471
|
+
display_available_commands() {
|
|
1472
|
+
echo "${BLUE}ACP Commands Available:${NC}"
|
|
1473
|
+
echo ""
|
|
1474
|
+
echo " ${GREEN}@acp.init${NC} - Initialize agent context (start here!)"
|
|
1475
|
+
echo " ${GREEN}@acp.proceed${NC} - Continue with next task"
|
|
1476
|
+
echo " ${GREEN}@acp.status${NC} - Display project status"
|
|
1477
|
+
echo " ${GREEN}@acp.update${NC} - Update progress tracking"
|
|
1478
|
+
echo " ${GREEN}@acp.sync${NC} - Sync documentation with code"
|
|
1479
|
+
echo " ${GREEN}@acp.validate${NC} - Validate ACP documents"
|
|
1480
|
+
echo " ${GREEN}@acp.report${NC} - Generate project report"
|
|
1481
|
+
echo " ${GREEN}@acp.version-check${NC} - Show current ACP version"
|
|
1482
|
+
echo " ${GREEN}@acp.version-check-for-updates${NC} - Check for ACP updates"
|
|
1483
|
+
echo " ${GREEN}@acp.version-update${NC} - Update ACP to latest version"
|
|
1484
|
+
echo ""
|
|
1485
|
+
echo "${BLUE}Package Management Commands:${NC}"
|
|
1486
|
+
echo ""
|
|
1487
|
+
echo " ${GREEN}@acp.package-install${NC} - Install ACP packages from GitHub"
|
|
1488
|
+
echo " ${GREEN}@acp.package-list${NC} - List installed packages"
|
|
1489
|
+
echo " ${GREEN}@acp.package-update${NC} - Update installed packages"
|
|
1490
|
+
echo " ${GREEN}@acp.package-remove${NC} - Remove installed packages"
|
|
1491
|
+
echo " ${GREEN}@acp.package-info${NC} - Show package details"
|
|
1492
|
+
echo " ${GREEN}@acp.package-search${NC} - Search for packages on GitHub"
|
|
1493
|
+
echo ""
|
|
1494
|
+
echo "${BLUE}Git Commands Available:${NC}"
|
|
1495
|
+
echo ""
|
|
1496
|
+
echo " ${GREEN}@git.init${NC} - Initialize git repository with smart .gitignore"
|
|
1497
|
+
echo " ${GREEN}@git.commit${NC} - Intelligent version-aware git commit"
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
# ============================================================================
|
|
1501
|
+
# Pre-Commit Hook System
|
|
1502
|
+
# ============================================================================
|
|
1503
|
+
|
|
1504
|
+
# Install pre-commit hook for package validation
|
|
1505
|
+
# Usage: install_precommit_hook
|
|
1506
|
+
# Returns: 0 on success, 1 on failure
|
|
1507
|
+
install_precommit_hook() {
|
|
1508
|
+
local hook_file=".git/hooks/pre-commit"
|
|
1509
|
+
|
|
1510
|
+
# Check if .git directory exists
|
|
1511
|
+
if [ ! -d ".git" ]; then
|
|
1512
|
+
echo "${RED}Error: Not a git repository${NC}" >&2
|
|
1513
|
+
return 1
|
|
1514
|
+
fi
|
|
1515
|
+
|
|
1516
|
+
# Create hooks directory if it doesn't exist
|
|
1517
|
+
mkdir -p ".git/hooks"
|
|
1518
|
+
|
|
1519
|
+
# Check if hook already exists
|
|
1520
|
+
if [ -f "$hook_file" ]; then
|
|
1521
|
+
echo "${YELLOW}⚠ Pre-commit hook already exists${NC}"
|
|
1522
|
+
echo " Backing up to pre-commit.backup"
|
|
1523
|
+
cp "$hook_file" "${hook_file}.backup"
|
|
1524
|
+
fi
|
|
1525
|
+
|
|
1526
|
+
# Create hook from template
|
|
1527
|
+
cat > "$hook_file" << 'EOF'
|
|
1528
|
+
#!/bin/sh
|
|
1529
|
+
# ACP Package Pre-Commit Hook
|
|
1530
|
+
# Validates package.yaml before allowing commit
|
|
1531
|
+
|
|
1532
|
+
# Colors for output
|
|
1533
|
+
if command -v tput >/dev/null 2>&1 && [ -t 1 ]; then
|
|
1534
|
+
RED=$(tput setaf 1)
|
|
1535
|
+
GREEN=$(tput setaf 2)
|
|
1536
|
+
YELLOW=$(tput setaf 3)
|
|
1537
|
+
NC=$(tput sgr0)
|
|
1538
|
+
else
|
|
1539
|
+
RED=''
|
|
1540
|
+
GREEN=''
|
|
1541
|
+
YELLOW=''
|
|
1542
|
+
NC=''
|
|
1543
|
+
fi
|
|
1544
|
+
|
|
1545
|
+
# Check if package.yaml exists
|
|
1546
|
+
if [ ! -f "package.yaml" ]; then
|
|
1547
|
+
# Not a package directory, skip validation
|
|
1548
|
+
exit 0
|
|
1549
|
+
fi
|
|
1550
|
+
|
|
1551
|
+
# Check if validation script exists
|
|
1552
|
+
if [ ! -f "agent/scripts/acp.yaml-validate.sh" ]; then
|
|
1553
|
+
echo "${YELLOW}Warning: acp.yaml-validate.sh not found, skipping validation${NC}"
|
|
1554
|
+
exit 0
|
|
1555
|
+
fi
|
|
1556
|
+
|
|
1557
|
+
# Check if schema exists
|
|
1558
|
+
if [ ! -f "agent/schemas/package.schema.yaml" ]; then
|
|
1559
|
+
echo "${YELLOW}Warning: package.schema.yaml not found, skipping validation${NC}"
|
|
1560
|
+
exit 0
|
|
1561
|
+
fi
|
|
1562
|
+
|
|
1563
|
+
# Validate package.yaml by running the script directly (not sourcing)
|
|
1564
|
+
echo "Validating package.yaml..."
|
|
1565
|
+
if ! ./agent/scripts/acp.yaml-validate.sh "package.yaml" "agent/schemas/package.schema.yaml" 2>/dev/null; then
|
|
1566
|
+
echo ""
|
|
1567
|
+
echo "${RED}✗ Pre-commit validation failed${NC}"
|
|
1568
|
+
echo ""
|
|
1569
|
+
echo "package.yaml has validation errors."
|
|
1570
|
+
echo "Please fix the errors and try again."
|
|
1571
|
+
echo ""
|
|
1572
|
+
echo "To see detailed errors, run:"
|
|
1573
|
+
echo " ./agent/scripts/acp.yaml-validate.sh package.yaml agent/schemas/package.schema.yaml"
|
|
1574
|
+
echo ""
|
|
1575
|
+
exit 1
|
|
1576
|
+
fi
|
|
1577
|
+
|
|
1578
|
+
echo "${GREEN}✓${NC} package.yaml is valid"
|
|
1579
|
+
|
|
1580
|
+
# Future enhancements (documented for reference):
|
|
1581
|
+
# - Namespace consistency checking across all files
|
|
1582
|
+
# - CHANGELOG.md validation for version changes
|
|
1583
|
+
# - File existence verification (all files in package.yaml exist)
|
|
1584
|
+
# - README.md structure validation
|
|
1585
|
+
# - Prevent commits to non-release branches
|
|
1586
|
+
|
|
1587
|
+
exit 0
|
|
1588
|
+
EOF
|
|
1589
|
+
|
|
1590
|
+
# Make executable
|
|
1591
|
+
chmod +x "$hook_file"
|
|
1592
|
+
|
|
1593
|
+
echo "${GREEN}✓${NC} Installed pre-commit hook"
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
# ============================================================================
|
|
1597
|
+
# Project Registry Functions
|
|
1598
|
+
# ============================================================================
|
|
1599
|
+
|
|
1600
|
+
# Get path to projects registry
|
|
1601
|
+
# Usage: registry_path=$(get_projects_registry_path)
|
|
1602
|
+
get_projects_registry_path() {
|
|
1603
|
+
echo "$HOME/.acp/projects.yaml"
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
# Check if projects registry exists
|
|
1607
|
+
# Usage: if projects_registry_exists; then ...
|
|
1608
|
+
projects_registry_exists() {
|
|
1609
|
+
[ -f "$(get_projects_registry_path)" ]
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
# Initialize projects registry
|
|
1613
|
+
# Usage: init_projects_registry
|
|
1614
|
+
init_projects_registry() {
|
|
1615
|
+
local registry_path
|
|
1616
|
+
registry_path=$(get_projects_registry_path)
|
|
1617
|
+
|
|
1618
|
+
if [ -f "$registry_path" ]; then
|
|
1619
|
+
return 0 # Already exists
|
|
1620
|
+
fi
|
|
1621
|
+
|
|
1622
|
+
# Ensure ~/.acp/ exists
|
|
1623
|
+
mkdir -p "$HOME/.acp"
|
|
1624
|
+
|
|
1625
|
+
# Get timestamp
|
|
1626
|
+
local timestamp
|
|
1627
|
+
timestamp=$(get_timestamp)
|
|
1628
|
+
|
|
1629
|
+
# Create registry with timestamp
|
|
1630
|
+
cat > "$registry_path" << EOF
|
|
1631
|
+
# ACP Project Registry
|
|
1632
|
+
current_project: null
|
|
1633
|
+
projects:
|
|
1634
|
+
registry_version: 1.0.0
|
|
1635
|
+
last_updated: ${timestamp}
|
|
1636
|
+
EOF
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
# Get git remote origin URL for a directory
|
|
1640
|
+
# Usage: origin=$(get_git_origin "/path/to/repo")
|
|
1641
|
+
# Returns: Git remote origin URL, or empty string if not a git repo or no origin
|
|
1642
|
+
get_git_origin() {
|
|
1643
|
+
local dir="${1:-.}"
|
|
1644
|
+
if [ -d "$dir/.git" ] || git -C "$dir" rev-parse --git-dir >/dev/null 2>&1; then
|
|
1645
|
+
git -C "$dir" remote get-url origin 2>/dev/null || echo ""
|
|
1646
|
+
else
|
|
1647
|
+
echo ""
|
|
1648
|
+
fi
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
# Get current git branch for a directory
|
|
1652
|
+
# Usage: branch=$(get_git_branch "/path/to/repo")
|
|
1653
|
+
# Returns: Current branch name, or empty string if not a git repo
|
|
1654
|
+
get_git_branch() {
|
|
1655
|
+
local dir="${1:-.}"
|
|
1656
|
+
if [ -d "$dir/.git" ] || git -C "$dir" rev-parse --git-dir >/dev/null 2>&1; then
|
|
1657
|
+
git -C "$dir" branch --show-current 2>/dev/null || echo ""
|
|
1658
|
+
else
|
|
1659
|
+
echo ""
|
|
1660
|
+
fi
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
# Register project in registry
|
|
1664
|
+
# Usage: register_project "project-name" "/path/to/project" "project-type" "description" ["git_origin"] ["git_branch"]
|
|
1665
|
+
# NOTE: Caller must source acp.yaml-parser.sh before calling this function
|
|
1666
|
+
# git_origin and git_branch are optional; if omitted, auto-detected from project path
|
|
1667
|
+
register_project() {
|
|
1668
|
+
local project_name="$1"
|
|
1669
|
+
local project_path="$2"
|
|
1670
|
+
local project_type="$3"
|
|
1671
|
+
local project_description="$4"
|
|
1672
|
+
local git_origin="${5:-}"
|
|
1673
|
+
local git_branch="${6:-}"
|
|
1674
|
+
local registry_path
|
|
1675
|
+
registry_path=$(get_projects_registry_path)
|
|
1676
|
+
|
|
1677
|
+
# Initialize registry if needed
|
|
1678
|
+
if ! projects_registry_exists; then
|
|
1679
|
+
init_projects_registry
|
|
1680
|
+
fi
|
|
1681
|
+
|
|
1682
|
+
# Source YAML parser
|
|
1683
|
+
source_yaml_parser
|
|
1684
|
+
|
|
1685
|
+
# Get timestamp
|
|
1686
|
+
local timestamp
|
|
1687
|
+
timestamp=$(get_timestamp)
|
|
1688
|
+
|
|
1689
|
+
# Parse registry
|
|
1690
|
+
yaml_parse "$registry_path"
|
|
1691
|
+
|
|
1692
|
+
# Add project entry (yaml_set now creates missing nodes!)
|
|
1693
|
+
yaml_set "projects.${project_name}.path" "$project_path"
|
|
1694
|
+
yaml_set "projects.${project_name}.type" "$project_type"
|
|
1695
|
+
yaml_set "projects.${project_name}.description" "$project_description"
|
|
1696
|
+
yaml_set "projects.${project_name}.created" "$timestamp"
|
|
1697
|
+
yaml_set "projects.${project_name}.last_modified" "$timestamp"
|
|
1698
|
+
yaml_set "projects.${project_name}.last_accessed" "$timestamp"
|
|
1699
|
+
yaml_set "projects.${project_name}.status" "active"
|
|
1700
|
+
|
|
1701
|
+
# Auto-detect git origin/branch if not provided
|
|
1702
|
+
local expanded_path="${project_path/#\~/$HOME}"
|
|
1703
|
+
if [ -z "$git_origin" ] && [ -d "$expanded_path" ]; then
|
|
1704
|
+
git_origin=$(get_git_origin "$expanded_path")
|
|
1705
|
+
fi
|
|
1706
|
+
if [ -z "$git_branch" ] && [ -d "$expanded_path" ]; then
|
|
1707
|
+
git_branch=$(get_git_branch "$expanded_path")
|
|
1708
|
+
fi
|
|
1709
|
+
|
|
1710
|
+
# Set git fields if available
|
|
1711
|
+
if [ -n "$git_origin" ]; then
|
|
1712
|
+
yaml_set "projects.${project_name}.git_origin" "$git_origin"
|
|
1713
|
+
fi
|
|
1714
|
+
if [ -n "$git_branch" ]; then
|
|
1715
|
+
yaml_set "projects.${project_name}.git_branch" "$git_branch"
|
|
1716
|
+
fi
|
|
1717
|
+
|
|
1718
|
+
# Set as current project if first project
|
|
1719
|
+
local current
|
|
1720
|
+
current=$(yaml_get "$registry_path" "current_project" 2>/dev/null || echo "")
|
|
1721
|
+
current=$(echo "$current" | sed "s/^['\"]//; s/['\"]$//")
|
|
1722
|
+
if [ -z "$current" ] || [ "$current" = "null" ]; then
|
|
1723
|
+
yaml_set "current_project" "$project_name"
|
|
1724
|
+
fi
|
|
1725
|
+
|
|
1726
|
+
# Update registry timestamp
|
|
1727
|
+
yaml_set "last_updated" "$timestamp"
|
|
1728
|
+
|
|
1729
|
+
# Write changes
|
|
1730
|
+
yaml_write "$registry_path"
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
# Check if project exists in registry
|
|
1734
|
+
# Usage: if project_exists "project-name"; then ...
|
|
1735
|
+
project_exists() {
|
|
1736
|
+
local project_name="$1"
|
|
1737
|
+
local registry_path
|
|
1738
|
+
registry_path=$(get_projects_registry_path)
|
|
1739
|
+
|
|
1740
|
+
if ! projects_registry_exists; then
|
|
1741
|
+
return 1
|
|
1742
|
+
fi
|
|
1743
|
+
|
|
1744
|
+
grep -q "^ ${project_name}:" "$registry_path"
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
# Get current project name
|
|
1748
|
+
# Usage: current=$(get_current_project)
|
|
1749
|
+
get_current_project() {
|
|
1750
|
+
local registry_path
|
|
1751
|
+
registry_path=$(get_projects_registry_path)
|
|
1752
|
+
|
|
1753
|
+
if ! projects_registry_exists; then
|
|
1754
|
+
return 1
|
|
1755
|
+
fi
|
|
1756
|
+
|
|
1757
|
+
local current
|
|
1758
|
+
current=$(grep "^current_project:" "$registry_path" | awk '{print $2}')
|
|
1759
|
+
if [ -n "$current" ] && [ "$current" != "null" ]; then
|
|
1760
|
+
echo "$current"
|
|
1761
|
+
fi
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
# Get current project path
|
|
1765
|
+
# Usage: path=$(get_current_project_path)
|
|
1766
|
+
get_current_project_path() {
|
|
1767
|
+
local current
|
|
1768
|
+
current=$(get_current_project)
|
|
1769
|
+
|
|
1770
|
+
if [ -z "$current" ]; then
|
|
1771
|
+
pwd # Fallback to current directory
|
|
1772
|
+
return 0
|
|
1773
|
+
fi
|
|
1774
|
+
|
|
1775
|
+
local registry_path
|
|
1776
|
+
registry_path=$(get_projects_registry_path)
|
|
1777
|
+
local path
|
|
1778
|
+
path=$(awk "/^ ${current}:/,/^ [a-z]/ {if (/^ path:/) print \$2}" "$registry_path")
|
|
1779
|
+
# Expand ~ to HOME
|
|
1780
|
+
echo "$path" | sed "s|^~|$HOME|"
|
|
1781
|
+
}
|