crewly 1.4.61 → 1.4.63
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/config/skills/agent/content-calendar/execute.sh +2 -2
- package/config/skills/agent/marketing/brand-onboarding/SKILL.md +76 -0
- package/config/skills/agent/marketing/brand-onboarding/execute.sh +312 -0
- package/config/skills/agent/marketing/submit-for-approval/SKILL.md +73 -0
- package/config/skills/agent/marketing/submit-for-approval/execute.sh +138 -0
- package/config/skills/agent/marketing/weekly-content-planning/SKILL.md +52 -0
- package/config/skills/agent/marketing/weekly-content-planning/execute.sh +202 -0
- package/config/skills/agent/marketing/weekly-content-planning/execute.test.sh +151 -0
- package/config/skills/agent/marketing/weekly-marketing-report/SKILL.md +51 -0
- package/config/skills/agent/marketing/weekly-marketing-report/execute.sh +190 -0
- package/config/skills/agent/marketing/weekly-marketing-report/execute.test.sh +241 -0
- package/config/skills/orchestrator/send-to-remote/SKILL.md +51 -0
- package/config/skills/orchestrator/send-to-remote/execute.sh +114 -0
- package/config/templates/marketing-team/README.md +42 -0
- package/config/templates/marketing-team/goals.md +21 -0
- package/config/templates/marketing-team/knowledge/docs/brand-voice-guide.md +61 -0
- package/config/templates/marketing-team/knowledge/docs/content-best-practices.md +64 -0
- package/config/templates/marketing-team/knowledge/index.json +24 -0
- package/config/templates/marketing-team/learned-patterns.json +5 -0
- package/config/templates/marketing-team/norms/brand-consistency.md +40 -0
- package/config/templates/marketing-team/norms/content-quality-checklist.md +45 -0
- package/config/templates/marketing-team/quality-gates.yaml +47 -0
- package/config/templates/marketing-team/roles/analyst.md +63 -0
- package/config/templates/marketing-team/roles/strategist.md +26 -0
- package/config/templates/marketing-team/roles/writer.md +58 -0
- package/config/templates/marketing-team/template.json +90 -0
- package/config/templates/marketing-team/workflows/weekly-content-cycle.yaml +48 -0
- package/dist/backend/backend/src/constants.d.ts +9 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +9 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.d.ts +16 -0
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.js +140 -0
- package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.d.ts +7 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.js +7 -0
- package/dist/backend/backend/src/controllers/cross-machine/index.js.map +1 -0
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +3 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +46 -6
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +13 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +50 -5
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts +8 -0
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js +52 -3
- package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/relay-client.service.d.ts +5 -1
- package/dist/backend/backend/src/services/cloud/relay-client.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/relay-client.service.js +14 -2
- package/dist/backend/backend/src/services/cloud/relay-client.service.js.map +1 -1
- package/dist/backend/backend/src/services/index.d.ts +2 -0
- package/dist/backend/backend/src/services/index.d.ts.map +1 -1
- package/dist/backend/backend/src/services/index.js +2 -0
- package/dist/backend/backend/src/services/index.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.d.ts +155 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.js +469 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.d.ts +107 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.js +104 -0
- package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.d.ts +124 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.js +256 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.service.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.d.ts +80 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.js +16 -0
- package/dist/backend/backend/src/services/onboarding/content-approval.types.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/index.d.ts +12 -0
- package/dist/backend/backend/src/services/onboarding/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/index.js +10 -0
- package/dist/backend/backend/src/services/onboarding/index.js.map +1 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts +147 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js +306 -0
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +7 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +76 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +8 -2
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/types/cross-machine.types.d.ts +103 -0
- package/dist/backend/backend/src/types/cross-machine.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/cross-machine.types.js +47 -0
- package/dist/backend/backend/src/types/cross-machine.types.js.map +1 -0
- package/dist/cli/backend/src/constants.d.ts +9 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +9 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/cli/src/commands/cloud.d.ts +18 -2
- package/dist/cli/cli/src/commands/cloud.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/cloud.js +72 -16
- package/dist/cli/cli/src/commands/cloud.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Weekly Content Planning — generates a weekly content calendar with 5-7 posts
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
6
|
+
|
|
7
|
+
INPUT="${1:-}"
|
|
8
|
+
[ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"businessName\":\"...\",\"industry\":\"...\",\"platforms\":[\"x\",\"linkedin\"],\"weekStartDate\":\"YYYY-MM-DD\"}'"
|
|
9
|
+
|
|
10
|
+
# Parse parameters
|
|
11
|
+
BUSINESS_NAME=$(echo "$INPUT" | jq -r '.businessName // empty')
|
|
12
|
+
INDUSTRY=$(echo "$INPUT" | jq -r '.industry // empty')
|
|
13
|
+
PLATFORMS=$(echo "$INPUT" | jq -r '.platforms // empty')
|
|
14
|
+
CONTENT_MIX=$(echo "$INPUT" | jq -r '.contentMix // "{\"educational\":40,\"engagement\":30,\"promotional\":20,\"community\":10}"')
|
|
15
|
+
WEEK_START=$(echo "$INPUT" | jq -r '.weekStartDate // empty')
|
|
16
|
+
PROJECT_PATH=$(echo "$INPUT" | jq -r '.projectPath // empty')
|
|
17
|
+
POST_COUNT=$(echo "$INPUT" | jq -r '.postCount // "5"')
|
|
18
|
+
|
|
19
|
+
require_param "businessName" "$BUSINESS_NAME"
|
|
20
|
+
require_param "industry" "$INDUSTRY"
|
|
21
|
+
require_param "platforms" "$PLATFORMS"
|
|
22
|
+
require_param "weekStartDate" "$WEEK_START"
|
|
23
|
+
|
|
24
|
+
# Validate platforms is a JSON array
|
|
25
|
+
if ! echo "$PLATFORMS" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
26
|
+
error_exit "platforms must be a JSON array, e.g. [\"x\",\"linkedin\"]"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
PLATFORM_COUNT=$(echo "$PLATFORMS" | jq 'length')
|
|
30
|
+
if [ "$PLATFORM_COUNT" -eq 0 ]; then
|
|
31
|
+
error_exit "platforms array must not be empty"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Validate content mix
|
|
35
|
+
if ! echo "$CONTENT_MIX" | jq empty 2>/dev/null; then
|
|
36
|
+
CONTENT_MIX='{"educational":40,"engagement":30,"promotional":20,"community":10}'
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Path to content-calendar skill
|
|
40
|
+
CALENDAR_SKILL="${SCRIPT_DIR}/../../content-calendar/execute.sh"
|
|
41
|
+
if [ ! -f "$CALENDAR_SKILL" ]; then
|
|
42
|
+
error_exit "content-calendar skill not found at ${CALENDAR_SKILL}"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Helper: get topic by category
|
|
46
|
+
get_topic() {
|
|
47
|
+
local cat="$1"
|
|
48
|
+
case "$cat" in
|
|
49
|
+
educational) echo "Industry insight: ${INDUSTRY} trends and best practices" ;;
|
|
50
|
+
engagement) echo "Community question: What matters most to our ${INDUSTRY} audience" ;;
|
|
51
|
+
promotional) echo "Product spotlight: How ${BUSINESS_NAME} solves ${INDUSTRY} challenges" ;;
|
|
52
|
+
community) echo "Behind the scenes: A day at ${BUSINESS_NAME}" ;;
|
|
53
|
+
*) echo "Content for ${BUSINESS_NAME}" ;;
|
|
54
|
+
esac
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Compute content mix distribution
|
|
58
|
+
EDU_PCT=$(echo "$CONTENT_MIX" | jq -r '.educational // 40')
|
|
59
|
+
ENG_PCT=$(echo "$CONTENT_MIX" | jq -r '.engagement // 30')
|
|
60
|
+
PROMO_PCT=$(echo "$CONTENT_MIX" | jq -r '.promotional // 20')
|
|
61
|
+
COMM_PCT=$(echo "$CONTENT_MIX" | jq -r '.community // 10')
|
|
62
|
+
|
|
63
|
+
# Calculate post count per category
|
|
64
|
+
TOTAL_PCT=$((EDU_PCT + ENG_PCT + PROMO_PCT + COMM_PCT))
|
|
65
|
+
if [ "$TOTAL_PCT" -eq 0 ]; then
|
|
66
|
+
TOTAL_PCT=100
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
EDU_COUNT=$(( (POST_COUNT * EDU_PCT + TOTAL_PCT - 1) / TOTAL_PCT ))
|
|
70
|
+
ENG_COUNT=$(( (POST_COUNT * ENG_PCT + TOTAL_PCT - 1) / TOTAL_PCT ))
|
|
71
|
+
PROMO_COUNT=$(( (POST_COUNT * PROMO_PCT + TOTAL_PCT - 1) / TOTAL_PCT ))
|
|
72
|
+
COMM_COUNT=$(( POST_COUNT - EDU_COUNT - ENG_COUNT - PROMO_COUNT ))
|
|
73
|
+
[ "$COMM_COUNT" -lt 0 ] && COMM_COUNT=0
|
|
74
|
+
|
|
75
|
+
# Build ordered category list as space-separated string
|
|
76
|
+
CATEGORIES=""
|
|
77
|
+
i=0; while [ "$i" -lt "$EDU_COUNT" ]; do CATEGORIES="$CATEGORIES educational"; i=$((i+1)); done
|
|
78
|
+
i=0; while [ "$i" -lt "$ENG_COUNT" ]; do CATEGORIES="$CATEGORIES engagement"; i=$((i+1)); done
|
|
79
|
+
i=0; while [ "$i" -lt "$PROMO_COUNT" ]; do CATEGORIES="$CATEGORIES promotional"; i=$((i+1)); done
|
|
80
|
+
i=0; while [ "$i" -lt "$COMM_COUNT" ]; do CATEGORIES="$CATEGORIES community"; i=$((i+1)); done
|
|
81
|
+
|
|
82
|
+
# Trim whitespace and convert to array-like processing
|
|
83
|
+
CATEGORIES=$(echo "$CATEGORIES" | xargs)
|
|
84
|
+
|
|
85
|
+
# Trim to POST_COUNT
|
|
86
|
+
CAT_ARRAY=""
|
|
87
|
+
COUNT=0
|
|
88
|
+
for CAT in $CATEGORIES; do
|
|
89
|
+
if [ "$COUNT" -ge "$POST_COUNT" ]; then break; fi
|
|
90
|
+
CAT_ARRAY="$CAT_ARRAY $CAT"
|
|
91
|
+
COUNT=$((COUNT + 1))
|
|
92
|
+
done
|
|
93
|
+
CAT_ARRAY=$(echo "$CAT_ARRAY" | xargs)
|
|
94
|
+
TOTAL_ENTRIES=$COUNT
|
|
95
|
+
|
|
96
|
+
DAY_NAMES="Mon Tue Wed Thu Fri Sat Sun"
|
|
97
|
+
|
|
98
|
+
# Generate entries
|
|
99
|
+
ENTRIES="[]"
|
|
100
|
+
MARKDOWN_TABLE="| Day | Platform | Type | Category | Topic | Status |\n|-----|----------|------|----------|-------|--------|\n"
|
|
101
|
+
|
|
102
|
+
INDEX=0
|
|
103
|
+
for CATEGORY in $CAT_ARRAY; do
|
|
104
|
+
# Cycle through platforms
|
|
105
|
+
PLATFORM_INDEX=$((INDEX % PLATFORM_COUNT))
|
|
106
|
+
PLATFORM_RAW=$(echo "$PLATFORMS" | jq -r ".[$PLATFORM_INDEX]")
|
|
107
|
+
|
|
108
|
+
# Normalize platform display names to calendar skill identifiers
|
|
109
|
+
case "$(echo "$PLATFORM_RAW" | tr '[:upper:]' '[:lower:]')" in
|
|
110
|
+
"x (twitter)"|"x"|"twitter") PLATFORM="x" ;;
|
|
111
|
+
"linkedin") PLATFORM="linkedin" ;;
|
|
112
|
+
"instagram") PLATFORM="instagram" ;;
|
|
113
|
+
"facebook") PLATFORM="facebook" ;;
|
|
114
|
+
"youtube") PLATFORM="youtube" ;;
|
|
115
|
+
"xiaohongshu"|"小红书") PLATFORM="xiaohongshu" ;;
|
|
116
|
+
"substack") PLATFORM="substack" ;;
|
|
117
|
+
"reddit") PLATFORM="reddit" ;;
|
|
118
|
+
"github") PLATFORM="github" ;;
|
|
119
|
+
*) PLATFORM=$(echo "$PLATFORM_RAW" | tr '[:upper:]' '[:lower:]') ;;
|
|
120
|
+
esac
|
|
121
|
+
|
|
122
|
+
# Calculate the date offset (spread across the week)
|
|
123
|
+
DAY_OFFSET=$((INDEX * 7 / TOTAL_ENTRIES))
|
|
124
|
+
|
|
125
|
+
# Calculate scheduled date
|
|
126
|
+
if date -v+${DAY_OFFSET}d +%Y-%m-%d 2>/dev/null >/dev/null; then
|
|
127
|
+
# macOS: -v flag must come before -f
|
|
128
|
+
SCHED_DATE=$(date -j -v+${DAY_OFFSET}d -f "%Y-%m-%d" "$WEEK_START" +%Y-%m-%d 2>/dev/null || echo "$WEEK_START")
|
|
129
|
+
else
|
|
130
|
+
# GNU date
|
|
131
|
+
SCHED_DATE=$(date -d "$WEEK_START + $DAY_OFFSET days" +%Y-%m-%d 2>/dev/null || echo "$WEEK_START")
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Set content type based on platform
|
|
135
|
+
case "$PLATFORM" in
|
|
136
|
+
x) CONTENT_TYPE="post" ;;
|
|
137
|
+
linkedin) CONTENT_TYPE="post" ;;
|
|
138
|
+
instagram) CONTENT_TYPE="image-text" ;;
|
|
139
|
+
youtube) CONTENT_TYPE="video" ;;
|
|
140
|
+
substack) CONTENT_TYPE="newsletter" ;;
|
|
141
|
+
*) CONTENT_TYPE="post" ;;
|
|
142
|
+
esac
|
|
143
|
+
|
|
144
|
+
TOPIC=$(get_topic "$CATEGORY")
|
|
145
|
+
TITLE="${BUSINESS_NAME} — ${CATEGORY} content for ${PLATFORM} (Week of ${WEEK_START})"
|
|
146
|
+
|
|
147
|
+
# Get day name from offset
|
|
148
|
+
DAY_NAME=$(echo "$DAY_NAMES" | cut -d' ' -f$((DAY_OFFSET + 1)))
|
|
149
|
+
|
|
150
|
+
# Add to content calendar via skill
|
|
151
|
+
ADD_INPUT=$(jq -n \
|
|
152
|
+
--arg action "add" \
|
|
153
|
+
--arg title "$TITLE" \
|
|
154
|
+
--arg platform "$PLATFORM" \
|
|
155
|
+
--arg type "$CONTENT_TYPE" \
|
|
156
|
+
--arg scheduledDate "$SCHED_DATE" \
|
|
157
|
+
--arg status "draft" \
|
|
158
|
+
--arg topic "$TOPIC" \
|
|
159
|
+
--arg notes "Category: ${CATEGORY}. Auto-generated by weekly-content-planning skill." \
|
|
160
|
+
--arg projectPath "$PROJECT_PATH" \
|
|
161
|
+
'{action:$action,title:$title,platform:$platform,type:$type,scheduledDate:$scheduledDate,status:$status,topic:$topic,notes:$notes,projectPath:$projectPath}')
|
|
162
|
+
|
|
163
|
+
RESULT=$(bash "$CALENDAR_SKILL" "$ADD_INPUT" 2>/dev/null || echo '{"success":false}')
|
|
164
|
+
|
|
165
|
+
ENTRY_ID=$(echo "$RESULT" | jq -r '.entry.id // "unknown"')
|
|
166
|
+
|
|
167
|
+
# Build entry object
|
|
168
|
+
ENTRY=$(jq -n \
|
|
169
|
+
--arg id "$ENTRY_ID" \
|
|
170
|
+
--arg day "$DAY_NAME" \
|
|
171
|
+
--arg platform "$PLATFORM" \
|
|
172
|
+
--arg type "$CONTENT_TYPE" \
|
|
173
|
+
--arg category "$CATEGORY" \
|
|
174
|
+
--arg topic "$TOPIC" \
|
|
175
|
+
--arg title "$TITLE" \
|
|
176
|
+
--arg scheduledDate "$SCHED_DATE" \
|
|
177
|
+
--arg status "draft" \
|
|
178
|
+
'{id:$id,day:$day,platform:$platform,type:$type,category:$category,topic:$topic,title:$title,scheduledDate:$scheduledDate,status:$status}')
|
|
179
|
+
|
|
180
|
+
ENTRIES=$(echo "$ENTRIES" | jq --argjson e "$ENTRY" '. += [$e]')
|
|
181
|
+
MARKDOWN_TABLE="${MARKDOWN_TABLE}| ${DAY_NAME} | ${PLATFORM} | ${CONTENT_TYPE} | ${CATEGORY} | ${TOPIC} | draft |\n"
|
|
182
|
+
|
|
183
|
+
INDEX=$((INDEX + 1))
|
|
184
|
+
done
|
|
185
|
+
|
|
186
|
+
ENTRY_COUNT=$(echo "$ENTRIES" | jq 'length')
|
|
187
|
+
|
|
188
|
+
# Output result
|
|
189
|
+
jq -n \
|
|
190
|
+
--arg businessName "$BUSINESS_NAME" \
|
|
191
|
+
--arg weekStart "$WEEK_START" \
|
|
192
|
+
--argjson entries "$ENTRIES" \
|
|
193
|
+
--argjson entryCount "$ENTRY_COUNT" \
|
|
194
|
+
--arg calendar "$(echo -e "$MARKDOWN_TABLE")" \
|
|
195
|
+
'{
|
|
196
|
+
"success": true,
|
|
197
|
+
"businessName": $businessName,
|
|
198
|
+
"weekStartDate": $weekStart,
|
|
199
|
+
"entryCount": $entryCount,
|
|
200
|
+
"entries": $entries,
|
|
201
|
+
"calendar": $calendar
|
|
202
|
+
}'
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for weekly-content-planning skill
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
EXECUTE="${SCRIPT_DIR}/execute.sh"
|
|
7
|
+
TEST_PROJECT=$(mktemp -d)
|
|
8
|
+
PASS=0
|
|
9
|
+
FAIL=0
|
|
10
|
+
|
|
11
|
+
cleanup() {
|
|
12
|
+
rm -rf "$TEST_PROJECT"
|
|
13
|
+
}
|
|
14
|
+
trap cleanup EXIT
|
|
15
|
+
|
|
16
|
+
assert_eq() {
|
|
17
|
+
local test_name="$1" expected="$2" actual="$3"
|
|
18
|
+
if [ "$expected" = "$actual" ]; then
|
|
19
|
+
echo " ✓ ${test_name}"
|
|
20
|
+
PASS=$((PASS + 1))
|
|
21
|
+
else
|
|
22
|
+
echo " ✗ ${test_name}: expected '${expected}', got '${actual}'"
|
|
23
|
+
FAIL=$((FAIL + 1))
|
|
24
|
+
fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
assert_true() {
|
|
28
|
+
local test_name="$1" value="$2"
|
|
29
|
+
if [ "$value" = "true" ]; then
|
|
30
|
+
echo " ✓ ${test_name}"
|
|
31
|
+
PASS=$((PASS + 1))
|
|
32
|
+
else
|
|
33
|
+
echo " ✗ ${test_name}: expected true, got '${value}'"
|
|
34
|
+
FAIL=$((FAIL + 1))
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
assert_gte() {
|
|
39
|
+
local test_name="$1" expected="$2" actual="$3"
|
|
40
|
+
if [ "$actual" -ge "$expected" ] 2>/dev/null; then
|
|
41
|
+
echo " ✓ ${test_name}"
|
|
42
|
+
PASS=$((PASS + 1))
|
|
43
|
+
else
|
|
44
|
+
echo " ✗ ${test_name}: expected >= ${expected}, got '${actual}'"
|
|
45
|
+
FAIL=$((FAIL + 1))
|
|
46
|
+
fi
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
echo "=== Weekly Content Planning Skill Tests ==="
|
|
50
|
+
echo ""
|
|
51
|
+
|
|
52
|
+
# ─────────────────────────────────────────────
|
|
53
|
+
# Test 1: Missing required parameters
|
|
54
|
+
# ─────────────────────────────────────────────
|
|
55
|
+
echo "Test 1: Missing required parameters"
|
|
56
|
+
RESULT=$(bash "$EXECUTE" '{}' 2>&1 || true)
|
|
57
|
+
HAS_ERROR=$(echo "$RESULT" | jq -r '.error // empty' 2>/dev/null || echo "")
|
|
58
|
+
assert_true "exits with error on missing params" "$([ -n "$HAS_ERROR" ] && echo true || echo false)"
|
|
59
|
+
|
|
60
|
+
# ─────────────────────────────────────────────
|
|
61
|
+
# Test 2: Basic generation with 2 platforms
|
|
62
|
+
# ─────────────────────────────────────────────
|
|
63
|
+
echo "Test 2: Basic generation with 2 platforms"
|
|
64
|
+
RESULT=$(bash "$EXECUTE" "{
|
|
65
|
+
\"businessName\": \"TestCorp\",
|
|
66
|
+
\"industry\": \"technology\",
|
|
67
|
+
\"platforms\": [\"x\", \"linkedin\"],
|
|
68
|
+
\"weekStartDate\": \"2026-03-23\",
|
|
69
|
+
\"projectPath\": \"${TEST_PROJECT}\",
|
|
70
|
+
\"postCount\": \"5\"
|
|
71
|
+
}")
|
|
72
|
+
|
|
73
|
+
assert_true "returns success" "$(echo "$RESULT" | jq -r '.success')"
|
|
74
|
+
assert_eq "businessName matches" "TestCorp" "$(echo "$RESULT" | jq -r '.businessName')"
|
|
75
|
+
assert_eq "weekStartDate matches" "2026-03-23" "$(echo "$RESULT" | jq -r '.weekStartDate')"
|
|
76
|
+
assert_eq "generates 5 entries" "5" "$(echo "$RESULT" | jq -r '.entryCount')"
|
|
77
|
+
assert_true "has calendar markdown" "$(echo "$RESULT" | jq -r '.calendar' | grep -q 'Platform' && echo true || echo false)"
|
|
78
|
+
|
|
79
|
+
# Verify entries are written to calendar.json
|
|
80
|
+
CALENDAR_PATH="${TEST_PROJECT}/.crewly/content/calendar.json"
|
|
81
|
+
assert_true "calendar file created" "$([ -f "$CALENDAR_PATH" ] && echo true || echo false)"
|
|
82
|
+
CALENDAR_ENTRIES=$(jq '.entries | length' "$CALENDAR_PATH")
|
|
83
|
+
assert_eq "calendar has 5 entries" "5" "$CALENDAR_ENTRIES"
|
|
84
|
+
|
|
85
|
+
# ─────────────────────────────────────────────
|
|
86
|
+
# Test 3: Single platform
|
|
87
|
+
# ─────────────────────────────────────────────
|
|
88
|
+
echo "Test 3: Single platform"
|
|
89
|
+
TEST_PROJECT_2=$(mktemp -d)
|
|
90
|
+
RESULT=$(bash "$EXECUTE" "{
|
|
91
|
+
\"businessName\": \"Solo\",
|
|
92
|
+
\"industry\": \"marketing\",
|
|
93
|
+
\"platforms\": [\"x\"],
|
|
94
|
+
\"weekStartDate\": \"2026-04-01\",
|
|
95
|
+
\"projectPath\": \"${TEST_PROJECT_2}\",
|
|
96
|
+
\"postCount\": \"3\"
|
|
97
|
+
}")
|
|
98
|
+
|
|
99
|
+
assert_true "returns success" "$(echo "$RESULT" | jq -r '.success')"
|
|
100
|
+
assert_eq "generates 3 entries" "3" "$(echo "$RESULT" | jq -r '.entryCount')"
|
|
101
|
+
|
|
102
|
+
# All entries should be on x platform
|
|
103
|
+
ALL_X=$(echo "$RESULT" | jq '[.entries[] | select(.platform == "x")] | length')
|
|
104
|
+
assert_eq "all entries on x platform" "3" "$ALL_X"
|
|
105
|
+
rm -rf "$TEST_PROJECT_2"
|
|
106
|
+
|
|
107
|
+
# ─────────────────────────────────────────────
|
|
108
|
+
# Test 4: Invalid platforms format
|
|
109
|
+
# ─────────────────────────────────────────────
|
|
110
|
+
echo "Test 4: Invalid platforms format"
|
|
111
|
+
RESULT=$(bash "$EXECUTE" '{
|
|
112
|
+
"businessName": "Test",
|
|
113
|
+
"industry": "tech",
|
|
114
|
+
"platforms": "not-an-array",
|
|
115
|
+
"weekStartDate": "2026-03-23"
|
|
116
|
+
}' 2>&1 || true)
|
|
117
|
+
HAS_ERROR=$(echo "$RESULT" | jq -r '.error // empty' 2>/dev/null || echo "")
|
|
118
|
+
assert_true "errors on non-array platforms" "$([ -n "$HAS_ERROR" ] && echo true || echo false)"
|
|
119
|
+
|
|
120
|
+
# ─────────────────────────────────────────────
|
|
121
|
+
# Test 5: Entries have correct structure
|
|
122
|
+
# ─────────────────────────────────────────────
|
|
123
|
+
echo "Test 5: Entry structure validation"
|
|
124
|
+
TEST_PROJECT_5=$(mktemp -d)
|
|
125
|
+
RESULT=$(bash "$EXECUTE" "{
|
|
126
|
+
\"businessName\": \"StructTest\",
|
|
127
|
+
\"industry\": \"ai\",
|
|
128
|
+
\"platforms\": [\"linkedin\"],
|
|
129
|
+
\"weekStartDate\": \"2026-03-23\",
|
|
130
|
+
\"projectPath\": \"${TEST_PROJECT_5}\",
|
|
131
|
+
\"postCount\": \"2\"
|
|
132
|
+
}")
|
|
133
|
+
|
|
134
|
+
FIRST_ENTRY=$(echo "$RESULT" | jq '.entries[0]')
|
|
135
|
+
assert_true "entry has id" "$(echo "$FIRST_ENTRY" | jq -r 'has("id")' )"
|
|
136
|
+
assert_true "entry has platform" "$(echo "$FIRST_ENTRY" | jq -r 'has("platform")')"
|
|
137
|
+
assert_true "entry has category" "$(echo "$FIRST_ENTRY" | jq -r 'has("category")')"
|
|
138
|
+
assert_true "entry has topic" "$(echo "$FIRST_ENTRY" | jq -r 'has("topic")')"
|
|
139
|
+
assert_true "entry has scheduledDate" "$(echo "$FIRST_ENTRY" | jq -r 'has("scheduledDate")')"
|
|
140
|
+
assert_eq "entry status is draft" "draft" "$(echo "$FIRST_ENTRY" | jq -r '.status')"
|
|
141
|
+
rm -rf "$TEST_PROJECT_5"
|
|
142
|
+
|
|
143
|
+
# ─────────────────────────────────────────────
|
|
144
|
+
# Summary
|
|
145
|
+
# ─────────────────────────────────────────────
|
|
146
|
+
echo ""
|
|
147
|
+
echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
|
|
148
|
+
|
|
149
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: weekly-marketing-report
|
|
3
|
+
description: Generates a structured weekly marketing performance report by reading content calendar data and summarizing posts by platform, status, and completion rate.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: marketing
|
|
6
|
+
skillType: claude-skill
|
|
7
|
+
assignableRoles:
|
|
8
|
+
- analyst
|
|
9
|
+
triggers:
|
|
10
|
+
- weekly report
|
|
11
|
+
- marketing report
|
|
12
|
+
- performance report
|
|
13
|
+
- analytics report
|
|
14
|
+
tags:
|
|
15
|
+
- marketing
|
|
16
|
+
- analytics
|
|
17
|
+
- report
|
|
18
|
+
- weekly
|
|
19
|
+
execution:
|
|
20
|
+
type: script
|
|
21
|
+
script:
|
|
22
|
+
file: execute.sh
|
|
23
|
+
interpreter: bash
|
|
24
|
+
timeoutMs: 15000
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Weekly Marketing Report
|
|
28
|
+
|
|
29
|
+
Generates a structured weekly marketing performance report by reading content calendar data.
|
|
30
|
+
|
|
31
|
+
## Input Parameters
|
|
32
|
+
|
|
33
|
+
| Parameter | Required | Description |
|
|
34
|
+
|-----------|----------|-------------|
|
|
35
|
+
| `projectPath` | No | Project path to find calendar data (default: `~/.crewly`) |
|
|
36
|
+
| `weekEndDate` | Yes | End date of the reporting week in `YYYY-MM-DD` format |
|
|
37
|
+
| `businessName` | Yes | Name of the business |
|
|
38
|
+
| `calendarPath` | No | Custom path to calendar.json (overrides default) |
|
|
39
|
+
|
|
40
|
+
## Example
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bash execute.sh '{"projectPath":"/path/to/project","weekEndDate":"2026-03-29","businessName":"Acme AI"}'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Output
|
|
47
|
+
|
|
48
|
+
Returns JSON with:
|
|
49
|
+
- `success`: boolean
|
|
50
|
+
- `report`: structured report object with summary, platformBreakdown, topPerformers, recommendations
|
|
51
|
+
- `markdown`: formatted markdown report string
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Weekly Marketing Report — reads calendar data and generates a structured performance report
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
6
|
+
|
|
7
|
+
INPUT="${1:-}"
|
|
8
|
+
[ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"projectPath\":\"/path\",\"weekEndDate\":\"YYYY-MM-DD\",\"businessName\":\"...\"}'"
|
|
9
|
+
|
|
10
|
+
# Parse parameters
|
|
11
|
+
PROJECT_PATH=$(echo "$INPUT" | jq -r '.projectPath // empty')
|
|
12
|
+
WEEK_END=$(echo "$INPUT" | jq -r '.weekEndDate // empty')
|
|
13
|
+
BUSINESS_NAME=$(echo "$INPUT" | jq -r '.businessName // empty')
|
|
14
|
+
CALENDAR_PATH=$(echo "$INPUT" | jq -r '.calendarPath // empty')
|
|
15
|
+
|
|
16
|
+
require_param "weekEndDate" "$WEEK_END"
|
|
17
|
+
require_param "businessName" "$BUSINESS_NAME"
|
|
18
|
+
|
|
19
|
+
# Determine calendar path
|
|
20
|
+
if [ -z "$CALENDAR_PATH" ]; then
|
|
21
|
+
if [ -n "$PROJECT_PATH" ]; then
|
|
22
|
+
CALENDAR_PATH="${PROJECT_PATH}/.crewly/content/calendar.json"
|
|
23
|
+
else
|
|
24
|
+
CALENDAR_PATH="${HOME}/.crewly/content/calendar.json"
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Check if calendar file exists
|
|
29
|
+
if [ ! -f "$CALENDAR_PATH" ]; then
|
|
30
|
+
# Return empty report if no calendar data
|
|
31
|
+
jq -n \
|
|
32
|
+
--arg businessName "$BUSINESS_NAME" \
|
|
33
|
+
--arg weekEnd "$WEEK_END" \
|
|
34
|
+
'{
|
|
35
|
+
"success": true,
|
|
36
|
+
"report": {
|
|
37
|
+
"businessName": $businessName,
|
|
38
|
+
"weekEndDate": $weekEnd,
|
|
39
|
+
"summary": "No content calendar data found. Start by creating a weekly content plan.",
|
|
40
|
+
"totalPosts": 0,
|
|
41
|
+
"platformBreakdown": {},
|
|
42
|
+
"statusBreakdown": {},
|
|
43
|
+
"completionRate": 0,
|
|
44
|
+
"topPerformers": [],
|
|
45
|
+
"recommendations": ["Set up a content calendar using the weekly-content-planning skill.", "Define target platforms and content mix ratios.", "Establish baseline metrics for future comparison."]
|
|
46
|
+
},
|
|
47
|
+
"markdown": "# Weekly Marketing Report — Week ending '"$WEEK_END"'\n\n## Summary\nNo content calendar data found. Start by creating a weekly content plan.\n\n## Recommendations\n1. Set up a content calendar using the weekly-content-planning skill.\n2. Define target platforms and content mix ratios.\n3. Establish baseline metrics for future comparison."
|
|
48
|
+
}'
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Validate calendar JSON
|
|
53
|
+
if ! jq empty "$CALENDAR_PATH" 2>/dev/null; then
|
|
54
|
+
error_exit "Calendar file is not valid JSON: $CALENDAR_PATH"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Calculate week start (6 days before week end = 7-day window)
|
|
58
|
+
if date -v-6d +%Y-%m-%d 2>/dev/null >/dev/null; then
|
|
59
|
+
# macOS: -v flag must come before -f
|
|
60
|
+
WEEK_START=$(date -j -v-6d -f "%Y-%m-%d" "$WEEK_END" +%Y-%m-%d 2>/dev/null || echo "$WEEK_END")
|
|
61
|
+
else
|
|
62
|
+
# GNU/Linux date
|
|
63
|
+
WEEK_START=$(date -d "$WEEK_END - 6 days" +%Y-%m-%d 2>/dev/null || echo "$WEEK_END")
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Filter entries for this week
|
|
67
|
+
WEEK_ENTRIES=$(jq --arg from "$WEEK_START" --arg to "$WEEK_END" \
|
|
68
|
+
'[.entries[] | select(.scheduledDate >= $from and .scheduledDate <= $to)]' \
|
|
69
|
+
"$CALENDAR_PATH")
|
|
70
|
+
|
|
71
|
+
TOTAL_POSTS=$(echo "$WEEK_ENTRIES" | jq 'length')
|
|
72
|
+
|
|
73
|
+
# Posts by platform
|
|
74
|
+
BY_PLATFORM=$(echo "$WEEK_ENTRIES" | jq '[.[] | .platform] | group_by(.) | map({(.[0]): length}) | add // {}')
|
|
75
|
+
|
|
76
|
+
# Posts by status
|
|
77
|
+
BY_STATUS=$(echo "$WEEK_ENTRIES" | jq '[.[] | .status] | group_by(.) | map({(.[0]): length}) | add // {}')
|
|
78
|
+
|
|
79
|
+
# Completion rate (published / total)
|
|
80
|
+
PUBLISHED_COUNT=$(echo "$WEEK_ENTRIES" | jq '[.[] | select(.status == "published")] | length')
|
|
81
|
+
if [ "$TOTAL_POSTS" -gt 0 ]; then
|
|
82
|
+
COMPLETION_RATE=$(( (PUBLISHED_COUNT * 100) / TOTAL_POSTS ))
|
|
83
|
+
else
|
|
84
|
+
COMPLETION_RATE=0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Posts by content type
|
|
88
|
+
BY_TYPE=$(echo "$WEEK_ENTRIES" | jq '[.[] | .type] | group_by(.) | map({(.[0]): length}) | add // {}')
|
|
89
|
+
|
|
90
|
+
# Draft/ready/approved breakdown for pipeline health
|
|
91
|
+
DRAFT_COUNT=$(echo "$WEEK_ENTRIES" | jq '[.[] | select(.status == "draft")] | length')
|
|
92
|
+
READY_COUNT=$(echo "$WEEK_ENTRIES" | jq '[.[] | select(.status == "ready")] | length')
|
|
93
|
+
APPROVED_COUNT=$(echo "$WEEK_ENTRIES" | jq '[.[] | select(.status == "approved")] | length')
|
|
94
|
+
|
|
95
|
+
# Top performers — published entries (most recently published)
|
|
96
|
+
TOP_PERFORMERS=$(echo "$WEEK_ENTRIES" | jq '[.[] | select(.status == "published")] | sort_by(.publishedAt) | reverse | .[:3] | [.[] | {title: .title, platform: .platform, type: .type, scheduledDate: .scheduledDate}]')
|
|
97
|
+
|
|
98
|
+
# Generate recommendations
|
|
99
|
+
RECOMMENDATIONS="[]"
|
|
100
|
+
|
|
101
|
+
if [ "$TOTAL_POSTS" -eq 0 ]; then
|
|
102
|
+
RECOMMENDATIONS=$(echo "$RECOMMENDATIONS" | jq '. += ["No posts scheduled this week. Create a content plan using the weekly-content-planning skill."]')
|
|
103
|
+
elif [ "$TOTAL_POSTS" -lt 5 ]; then
|
|
104
|
+
RECOMMENDATIONS=$(echo "$RECOMMENDATIONS" | jq --argjson count "$TOTAL_POSTS" '. += ["Only \($count) posts scheduled. Target 5-7 posts per week for consistent presence."]')
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [ "$DRAFT_COUNT" -gt 0 ]; then
|
|
108
|
+
RECOMMENDATIONS=$(echo "$RECOMMENDATIONS" | jq --argjson count "$DRAFT_COUNT" '. += ["\($count) posts still in draft. Prioritize moving them to ready status."]')
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
if [ "$COMPLETION_RATE" -lt 50 ] && [ "$TOTAL_POSTS" -gt 0 ]; then
|
|
112
|
+
RECOMMENDATIONS=$(echo "$RECOMMENDATIONS" | jq --argjson rate "$COMPLETION_RATE" '. += ["Completion rate is \($rate)%. Investigate bottlenecks in the content pipeline."]')
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
PLATFORM_COUNT=$(echo "$BY_PLATFORM" | jq 'keys | length')
|
|
116
|
+
if [ "$PLATFORM_COUNT" -le 1 ] && [ "$TOTAL_POSTS" -gt 0 ]; then
|
|
117
|
+
RECOMMENDATIONS=$(echo "$RECOMMENDATIONS" | jq '. += ["Content concentrated on single platform. Diversify across 2-3 platforms for wider reach."]')
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# If no specific recommendations, add general ones
|
|
121
|
+
REC_COUNT=$(echo "$RECOMMENDATIONS" | jq 'length')
|
|
122
|
+
if [ "$REC_COUNT" -eq 0 ]; then
|
|
123
|
+
RECOMMENDATIONS='["Continue current posting cadence — metrics are on track.", "Experiment with one new content format next week.", "Review engagement data to identify top-performing content themes."]'
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Build markdown report
|
|
127
|
+
MARKDOWN="# Weekly Marketing Report — ${BUSINESS_NAME}\n"
|
|
128
|
+
MARKDOWN="${MARKDOWN}## Week of ${WEEK_START} to ${WEEK_END}\n\n"
|
|
129
|
+
MARKDOWN="${MARKDOWN}## Executive Summary\n"
|
|
130
|
+
MARKDOWN="${MARKDOWN}Total posts scheduled: ${TOTAL_POSTS} | Published: ${PUBLISHED_COUNT} | Completion rate: ${COMPLETION_RATE}%\n\n"
|
|
131
|
+
MARKDOWN="${MARKDOWN}## Platform Breakdown\n"
|
|
132
|
+
MARKDOWN="${MARKDOWN}$(echo "$BY_PLATFORM" | jq -r 'to_entries | .[] | "- **\(.key)**: \(.value) posts"')\n\n"
|
|
133
|
+
MARKDOWN="${MARKDOWN}## Status Breakdown\n"
|
|
134
|
+
MARKDOWN="${MARKDOWN}$(echo "$BY_STATUS" | jq -r 'to_entries | .[] | "- **\(.key)**: \(.value)"')\n\n"
|
|
135
|
+
MARKDOWN="${MARKDOWN}## Pipeline Health\n"
|
|
136
|
+
MARKDOWN="${MARKDOWN}| Stage | Count |\n|-------|-------|\n"
|
|
137
|
+
MARKDOWN="${MARKDOWN}| Draft | ${DRAFT_COUNT} |\n"
|
|
138
|
+
MARKDOWN="${MARKDOWN}| Ready | ${READY_COUNT} |\n"
|
|
139
|
+
MARKDOWN="${MARKDOWN}| Approved | ${APPROVED_COUNT} |\n"
|
|
140
|
+
MARKDOWN="${MARKDOWN}| Published | ${PUBLISHED_COUNT} |\n\n"
|
|
141
|
+
|
|
142
|
+
if [ "$(echo "$TOP_PERFORMERS" | jq 'length')" -gt 0 ]; then
|
|
143
|
+
MARKDOWN="${MARKDOWN}## Top Performers\n"
|
|
144
|
+
MARKDOWN="${MARKDOWN}$(echo "$TOP_PERFORMERS" | jq -r '.[] | "- **\(.title)** — \(.platform) (\(.type))"')\n\n"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
MARKDOWN="${MARKDOWN}## Recommendations\n"
|
|
148
|
+
MARKDOWN="${MARKDOWN}$(echo "$RECOMMENDATIONS" | jq -r 'to_entries | .[] | "\(.key + 1). \(.value)"')\n"
|
|
149
|
+
|
|
150
|
+
# Build structured report JSON
|
|
151
|
+
jq -n \
|
|
152
|
+
--arg businessName "$BUSINESS_NAME" \
|
|
153
|
+
--arg weekStart "$WEEK_START" \
|
|
154
|
+
--arg weekEnd "$WEEK_END" \
|
|
155
|
+
--argjson totalPosts "$TOTAL_POSTS" \
|
|
156
|
+
--argjson publishedCount "$PUBLISHED_COUNT" \
|
|
157
|
+
--argjson completionRate "$COMPLETION_RATE" \
|
|
158
|
+
--argjson platformBreakdown "$BY_PLATFORM" \
|
|
159
|
+
--argjson statusBreakdown "$BY_STATUS" \
|
|
160
|
+
--argjson typeBreakdown "$BY_TYPE" \
|
|
161
|
+
--argjson topPerformers "$TOP_PERFORMERS" \
|
|
162
|
+
--argjson recommendations "$RECOMMENDATIONS" \
|
|
163
|
+
--argjson draftCount "$DRAFT_COUNT" \
|
|
164
|
+
--argjson readyCount "$READY_COUNT" \
|
|
165
|
+
--argjson approvedCount "$APPROVED_COUNT" \
|
|
166
|
+
--arg markdown "$(echo -e "$MARKDOWN")" \
|
|
167
|
+
'{
|
|
168
|
+
"success": true,
|
|
169
|
+
"report": {
|
|
170
|
+
"businessName": $businessName,
|
|
171
|
+
"weekStartDate": $weekStart,
|
|
172
|
+
"weekEndDate": $weekEnd,
|
|
173
|
+
"summary": "Total: \($totalPosts) posts | Published: \($publishedCount) | Completion: \($completionRate)%",
|
|
174
|
+
"totalPosts": $totalPosts,
|
|
175
|
+
"publishedCount": $publishedCount,
|
|
176
|
+
"completionRate": $completionRate,
|
|
177
|
+
"platformBreakdown": $platformBreakdown,
|
|
178
|
+
"statusBreakdown": $statusBreakdown,
|
|
179
|
+
"typeBreakdown": $typeBreakdown,
|
|
180
|
+
"pipelineHealth": {
|
|
181
|
+
"draft": $draftCount,
|
|
182
|
+
"ready": $readyCount,
|
|
183
|
+
"approved": $approvedCount,
|
|
184
|
+
"published": $publishedCount
|
|
185
|
+
},
|
|
186
|
+
"topPerformers": $topPerformers,
|
|
187
|
+
"recommendations": $recommendations
|
|
188
|
+
},
|
|
189
|
+
"markdown": $markdown
|
|
190
|
+
}'
|