crewly 1.4.60 → 1.4.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/config/skills/agent/content-calendar/execute.sh +2 -2
  2. package/config/skills/agent/marketing/brand-onboarding/SKILL.md +76 -0
  3. package/config/skills/agent/marketing/brand-onboarding/execute.sh +312 -0
  4. package/config/skills/agent/marketing/submit-for-approval/SKILL.md +73 -0
  5. package/config/skills/agent/marketing/submit-for-approval/execute.sh +138 -0
  6. package/config/skills/agent/marketing/weekly-content-planning/SKILL.md +52 -0
  7. package/config/skills/agent/marketing/weekly-content-planning/execute.sh +202 -0
  8. package/config/skills/agent/marketing/weekly-content-planning/execute.test.sh +151 -0
  9. package/config/skills/agent/marketing/weekly-marketing-report/SKILL.md +51 -0
  10. package/config/skills/agent/marketing/weekly-marketing-report/execute.sh +190 -0
  11. package/config/skills/agent/marketing/weekly-marketing-report/execute.test.sh +241 -0
  12. package/config/skills/orchestrator/send-to-remote/SKILL.md +51 -0
  13. package/config/skills/orchestrator/send-to-remote/execute.sh +114 -0
  14. package/config/templates/marketing-team/README.md +42 -0
  15. package/config/templates/marketing-team/goals.md +21 -0
  16. package/config/templates/marketing-team/knowledge/docs/brand-voice-guide.md +61 -0
  17. package/config/templates/marketing-team/knowledge/docs/content-best-practices.md +64 -0
  18. package/config/templates/marketing-team/knowledge/index.json +24 -0
  19. package/config/templates/marketing-team/learned-patterns.json +5 -0
  20. package/config/templates/marketing-team/norms/brand-consistency.md +40 -0
  21. package/config/templates/marketing-team/norms/content-quality-checklist.md +45 -0
  22. package/config/templates/marketing-team/quality-gates.yaml +47 -0
  23. package/config/templates/marketing-team/roles/analyst.md +63 -0
  24. package/config/templates/marketing-team/roles/strategist.md +26 -0
  25. package/config/templates/marketing-team/roles/writer.md +58 -0
  26. package/config/templates/marketing-team/template.json +90 -0
  27. package/config/templates/marketing-team/workflows/weekly-content-cycle.yaml +48 -0
  28. package/dist/backend/backend/src/constants.d.ts +9 -0
  29. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  30. package/dist/backend/backend/src/constants.js +9 -0
  31. package/dist/backend/backend/src/constants.js.map +1 -1
  32. package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.d.ts +16 -0
  33. package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.d.ts.map +1 -0
  34. package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.js +140 -0
  35. package/dist/backend/backend/src/controllers/cross-machine/cross-machine.controller.js.map +1 -0
  36. package/dist/backend/backend/src/controllers/cross-machine/index.d.ts +7 -0
  37. package/dist/backend/backend/src/controllers/cross-machine/index.d.ts.map +1 -0
  38. package/dist/backend/backend/src/controllers/cross-machine/index.js +7 -0
  39. package/dist/backend/backend/src/controllers/cross-machine/index.js.map +1 -0
  40. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  41. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  42. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  43. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  44. package/dist/backend/backend/src/services/agent/agent-registration.service.js +46 -6
  45. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  46. package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +13 -0
  47. package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
  48. package/dist/backend/backend/src/services/ai/prompt-builder.service.js +50 -5
  49. package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
  50. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts +8 -0
  51. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js +52 -3
  53. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js.map +1 -1
  54. package/dist/backend/backend/src/services/cloud/relay-client.service.d.ts +5 -1
  55. package/dist/backend/backend/src/services/cloud/relay-client.service.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/cloud/relay-client.service.js +14 -2
  57. package/dist/backend/backend/src/services/cloud/relay-client.service.js.map +1 -1
  58. package/dist/backend/backend/src/services/index.d.ts +2 -0
  59. package/dist/backend/backend/src/services/index.d.ts.map +1 -1
  60. package/dist/backend/backend/src/services/index.js +2 -0
  61. package/dist/backend/backend/src/services/index.js.map +1 -1
  62. package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.d.ts +155 -0
  63. package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.d.ts.map +1 -0
  64. package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.js +469 -0
  65. package/dist/backend/backend/src/services/onboarding/brand-onboarding.service.js.map +1 -0
  66. package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.d.ts +107 -0
  67. package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.d.ts.map +1 -0
  68. package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.js +104 -0
  69. package/dist/backend/backend/src/services/onboarding/brand-onboarding.types.js.map +1 -0
  70. package/dist/backend/backend/src/services/onboarding/content-approval.service.d.ts +124 -0
  71. package/dist/backend/backend/src/services/onboarding/content-approval.service.d.ts.map +1 -0
  72. package/dist/backend/backend/src/services/onboarding/content-approval.service.js +256 -0
  73. package/dist/backend/backend/src/services/onboarding/content-approval.service.js.map +1 -0
  74. package/dist/backend/backend/src/services/onboarding/content-approval.types.d.ts +80 -0
  75. package/dist/backend/backend/src/services/onboarding/content-approval.types.d.ts.map +1 -0
  76. package/dist/backend/backend/src/services/onboarding/content-approval.types.js +16 -0
  77. package/dist/backend/backend/src/services/onboarding/content-approval.types.js.map +1 -0
  78. package/dist/backend/backend/src/services/onboarding/index.d.ts +12 -0
  79. package/dist/backend/backend/src/services/onboarding/index.d.ts.map +1 -0
  80. package/dist/backend/backend/src/services/onboarding/index.js +10 -0
  81. package/dist/backend/backend/src/services/onboarding/index.js.map +1 -0
  82. package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts +147 -0
  83. package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -0
  84. package/dist/backend/backend/src/services/slack/cross-machine-message.service.js +306 -0
  85. package/dist/backend/backend/src/services/slack/cross-machine-message.service.js.map +1 -0
  86. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +7 -0
  87. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  88. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +76 -0
  89. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  90. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/slack/slack.service.js +8 -2
  92. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  93. package/dist/backend/backend/src/types/cross-machine.types.d.ts +103 -0
  94. package/dist/backend/backend/src/types/cross-machine.types.d.ts.map +1 -0
  95. package/dist/backend/backend/src/types/cross-machine.types.js +47 -0
  96. package/dist/backend/backend/src/types/cross-machine.types.js.map +1 -0
  97. package/dist/cli/backend/src/constants.d.ts +9 -0
  98. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  99. package/dist/cli/backend/src/constants.js +9 -0
  100. package/dist/cli/backend/src/constants.js.map +1 -1
  101. package/dist/cli/cli/src/commands/cloud.d.ts +94 -0
  102. package/dist/cli/cli/src/commands/cloud.d.ts.map +1 -0
  103. package/dist/cli/cli/src/commands/cloud.js +323 -0
  104. package/dist/cli/cli/src/commands/cloud.js.map +1 -0
  105. package/dist/cli/cli/src/index.js +17 -0
  106. package/dist/cli/cli/src/index.js.map +1 -1
  107. package/package.json +1 -1
@@ -58,9 +58,9 @@ case "$ACTION" in
58
58
  require_param "scheduledDate" "$SCHEDULED_DATE"
59
59
 
60
60
  # Validate platform
61
- VALID_PLATFORMS="x|linkedin|xiaohongshu|substack|youtube|github|reddit"
61
+ VALID_PLATFORMS="x|linkedin|xiaohongshu|substack|youtube|github|reddit|instagram|facebook"
62
62
  if ! echo "$PLATFORM" | grep -qE "^(${VALID_PLATFORMS})$"; then
63
- error_exit "Invalid platform: $PLATFORM. Valid: x, linkedin, xiaohongshu, substack, youtube, github, reddit"
63
+ error_exit "Invalid platform: $PLATFORM. Valid: x, linkedin, instagram, facebook, xiaohongshu, substack, youtube, github, reddit"
64
64
  fi
65
65
 
66
66
  # Validate status
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: brand-onboarding
3
+ description: Interactive brand onboarding questionnaire that collects business information and generates a Brand Voice Guide for marketing team agents.
4
+ version: 1.0.0
5
+ category: marketing
6
+ assignableRoles:
7
+ - strategist
8
+ - orchestrator
9
+ triggers:
10
+ - on_team_create
11
+ - manual
12
+ tags:
13
+ - marketing
14
+ - onboarding
15
+ - brand
16
+ - setup
17
+ execution:
18
+ type: script
19
+ script: execute.sh
20
+ interpreter: bash
21
+ timeout: 60000
22
+ ---
23
+
24
+ # Brand Onboarding
25
+
26
+ Collects brand information from the business owner and generates a Brand Voice Guide
27
+ that all marketing team agents use to maintain brand consistency.
28
+
29
+ ## Usage
30
+
31
+ ### Start onboarding session
32
+ ```bash
33
+ bash execute.sh '{"action":"start","teamId":"team-123","templateId":"marketing-team"}'
34
+ ```
35
+
36
+ ### Submit an answer (step-by-step)
37
+ ```bash
38
+ bash execute.sh '{"action":"answer","sessionId":"session-uuid","value":"Sunrise Bakery"}'
39
+ ```
40
+
41
+ ### Submit all answers at once (batch mode)
42
+ ```bash
43
+ bash execute.sh '{"action":"batch","teamId":"team-123","templateId":"marketing-team","answers":{"business_name":"Sunrise Bakery","industry":"Food","description":"Artisan bakery","target_customer":"Health-conscious millennials","competitors":"Blue Apron, HelloFresh","personality":"Warm, Authentic, Passionate","tone":"casual","goals":"Brand awareness, Community building","platforms":"Instagram, X (Twitter)","content_examples":"https://example.com"}}'
44
+ ```
45
+
46
+ ### Complete onboarding (generate guide)
47
+ ```bash
48
+ bash execute.sh '{"action":"complete","sessionId":"session-uuid","outputDir":"/path/to/knowledge/docs"}'
49
+ ```
50
+
51
+ ### Get current question
52
+ ```bash
53
+ bash execute.sh '{"action":"question","sessionId":"session-uuid"}'
54
+ ```
55
+
56
+ ## Questions (10 total)
57
+
58
+ 1. Business name
59
+ 2. Industry
60
+ 3. One-sentence business description
61
+ 4. Target customer profile
62
+ 5. Top 3 competitors
63
+ 6. Brand personality (3 words)
64
+ 7. Content tone (Formal/Casual/Playful/Authoritative)
65
+ 8. Marketing goals
66
+ 9. Social platforms
67
+ 10. Content examples
68
+
69
+ ## Output
70
+
71
+ Generates `brand-voice-guide.md` in the specified output directory with:
72
+ - Business Profile section
73
+ - Brand Voice (personality, tone, do's, don'ts)
74
+ - Content Strategy (goals, platforms, content mix ratios)
75
+ - Platform-specific Guidelines
76
+ - Writing Rules
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env bash
2
+ # =============================================================================
3
+ # brand-onboarding — Interactive brand questionnaire & voice guide generator
4
+ #
5
+ # Manages the onboarding session via the Crewly API's BrandOnboardingService.
6
+ # Falls back to local file-based operation if API is not available.
7
+ #
8
+ # Usage:
9
+ # bash execute.sh '{"action":"start","teamId":"team-123","templateId":"marketing-team"}'
10
+ # bash execute.sh '{"action":"answer","sessionId":"uuid","value":"Sunrise Bakery"}'
11
+ # bash execute.sh '{"action":"batch","teamId":"team-123","answers":{...}}'
12
+ # bash execute.sh '{"action":"complete","sessionId":"uuid","outputDir":"/path/to/docs"}'
13
+ # bash execute.sh '{"action":"question","sessionId":"uuid"}'
14
+ # =============================================================================
15
+
16
+ set -euo pipefail
17
+
18
+ INPUT="${1:-"{}"}"
19
+
20
+ # Parse input fields
21
+ ACTION=$(echo "$INPUT" | jq -r '.action // "start"')
22
+ TEAM_ID=$(echo "$INPUT" | jq -r '.teamId // ""')
23
+ TEMPLATE_ID=$(echo "$INPUT" | jq -r '.templateId // "marketing-team"')
24
+ SESSION_ID=$(echo "$INPUT" | jq -r '.sessionId // ""')
25
+ VALUE=$(echo "$INPUT" | jq -r '.value // ""')
26
+ OUTPUT_DIR=$(echo "$INPUT" | jq -r '.outputDir // ""')
27
+ PROJECT_PATH=$(echo "$INPUT" | jq -r '.projectPath // ""')
28
+
29
+ # Crewly API base URL
30
+ API_BASE="${CREWLY_API_URL:-http://localhost:3000}"
31
+
32
+ # Local storage directory
33
+ LOCAL_DIR="${PROJECT_PATH:-.}/.crewly/onboarding"
34
+
35
+ # =============================================================================
36
+ # Questions definition (matches BrandOnboardingService)
37
+ # =============================================================================
38
+ QUESTIONS='[
39
+ {"id":"business_name","text":"What is your business name?","required":true,"order":1},
40
+ {"id":"industry","text":"What industry are you in? (e.g., Fashion, Food, Fitness, Tech, Education)","required":true,"order":2},
41
+ {"id":"description","text":"Describe your business in one sentence.","required":true,"order":3},
42
+ {"id":"target_customer","text":"Who is your target customer? (Age, interests, problems they have)","required":true,"order":4},
43
+ {"id":"competitors","text":"What are your top 3 competitors?","required":false,"order":5},
44
+ {"id":"personality","text":"Describe your brand personality in 3 words. (e.g., Professional, Friendly, Bold)","required":true,"order":6},
45
+ {"id":"tone","text":"What tone should your content have? (Formal / Casual / Playful / Authoritative)","required":true,"order":7},
46
+ {"id":"goals","text":"What are your marketing goals? (Brand awareness / Lead generation / Sales / Community)","required":true,"order":8},
47
+ {"id":"platforms","text":"Which platforms do you use? (X / LinkedIn / Instagram / Facebook)","required":true,"order":9},
48
+ {"id":"content_examples","text":"Share 2-3 examples of content you like (URLs or descriptions).","required":false,"order":10}
49
+ ]'
50
+
51
+ # =============================================================================
52
+ # Helper: generate brand voice guide from answers
53
+ # =============================================================================
54
+ generate_guide() {
55
+ local ANSWERS_FILE="$1"
56
+ local OUT_DIR="$2"
57
+
58
+ # Extract answers
59
+ local BIZ_NAME=$(jq -r '.business_name // "Your Business"' "$ANSWERS_FILE")
60
+ local INDUSTRY=$(jq -r '.industry // "General"' "$ANSWERS_FILE")
61
+ local DESCRIPTION=$(jq -r '.description // ""' "$ANSWERS_FILE")
62
+ local TARGET=$(jq -r '.target_customer // ""' "$ANSWERS_FILE")
63
+ local COMPETITORS=$(jq -r '.competitors // "Not specified"' "$ANSWERS_FILE")
64
+ local PERSONALITY=$(jq -r '.personality // "Professional, Friendly"' "$ANSWERS_FILE")
65
+ local TONE=$(jq -r '.tone // "casual"' "$ANSWERS_FILE")
66
+ local GOALS=$(jq -r '.goals // "Brand awareness"' "$ANSWERS_FILE")
67
+ local PLATFORMS=$(jq -r '.platforms // "Instagram"' "$ANSWERS_FILE")
68
+ local EXAMPLES=$(jq -r '.content_examples // "Not specified"' "$ANSWERS_FILE")
69
+
70
+ # Generate tone-specific rules
71
+ local DOS=""
72
+ local DONTS=""
73
+ case "$(echo "$TONE" | tr '[:upper:]' '[:lower:]')" in
74
+ formal)
75
+ DOS="- Use complete sentences and proper grammar\n- Cite data and statistics to support claims"
76
+ DONTS="- Use slang, abbreviations, or internet speak\n- Use excessive emojis (max 1 per post)"
77
+ ;;
78
+ playful)
79
+ DOS="- Use humor, wordplay, and pop culture references\n- Include emojis to add personality"
80
+ DONTS="- Be serious or dry in tone\n- Make jokes that could offend or alienate"
81
+ ;;
82
+ authoritative)
83
+ DOS="- Lead with expertise and industry knowledge\n- Share original insights and thought leadership"
84
+ DONTS="- Hedge or use uncertain language (\"maybe\", \"perhaps\")\n- Copy competitor messaging or talking points"
85
+ ;;
86
+ *)
87
+ DOS="- Write like you are talking to a friend\n- Use contractions and conversational language"
88
+ DONTS="- Be overly corporate or stiff\n- Use jargon that your audience might not understand"
89
+ ;;
90
+ esac
91
+ DOS="${DOS}\n- Stay consistent with the brand personality across all platforms\n- Engage authentically with comments and messages"
92
+ DONTS="${DONTS}\n- Use generic AI-sounding language\n- Post identical content across all platforms (adapt per platform)"
93
+
94
+ mkdir -p "$OUT_DIR"
95
+
96
+ # Capitalize first letter of tone (compatible with bash 3.x)
97
+ TONE_CAP="$(echo "$TONE" | awk '{print toupper(substr($0,1,1)) tolower(substr($0,2))}')"
98
+
99
+ # Write guide using printf to avoid heredoc substitution issues
100
+ {
101
+ printf '# Brand Voice Guide — %s\n\n' "$BIZ_NAME"
102
+ printf '> Auto-generated by Crewly AI Marketing Team onboarding.\n'
103
+ printf '> Last updated: %s\n\n' "$(date +%Y-%m-%d)"
104
+ printf '## Business Profile\n\n'
105
+ printf '%s\n' "- **Name**: $BIZ_NAME"
106
+ printf '%s\n' "- **Industry**: $INDUSTRY"
107
+ printf '%s\n' "- **Description**: $DESCRIPTION"
108
+ printf '%s\n' "- **Target Customer**: $TARGET"
109
+ printf '%s\n\n' "- **Competitors**: $COMPETITORS"
110
+ printf '## Brand Voice\n\n'
111
+ printf '%s\n' "- **Personality**: $PERSONALITY"
112
+ printf '%s\n\n' "- **Tone**: $TONE_CAP"
113
+ printf "### Do's\n"
114
+ printf '%b\n\n' "$DOS"
115
+ printf "### Don'ts\n"
116
+ printf '%b\n\n' "$DONTS"
117
+ printf '## Content Strategy\n\n'
118
+ printf '%s\n' "- **Goals**: $GOALS"
119
+ printf '%s\n' "- **Platforms**: $PLATFORMS"
120
+ printf '%s\n\n' "- **Content Mix**: 60% Educational, 20% Behind-the-scenes, 15% Promotional, 5% Interactive"
121
+ printf '## Writing Rules\n\n'
122
+ printf '1. Always write as if you ARE %s, not about %s.\n' "$BIZ_NAME" "$BIZ_NAME"
123
+ printf '2. Match the %s tone consistently across all platforms.\n' "$TONE"
124
+ printf '3. Use the brand personality (%s) to guide word choices.\n' "$PERSONALITY"
125
+ printf '4. Never use generic AI cliches like "In today'\''s digital landscape" or "Unlock the power of".\n'
126
+ printf '5. Every post must have a clear purpose: educate, entertain, sell, or engage.\n'
127
+ printf '6. Include a call-to-action in at least 50%% of posts.\n'
128
+ printf '7. Use hashtags strategically — 3-5 for Twitter/X, 10-15 for Instagram, 2-3 for LinkedIn.\n\n'
129
+ printf '## Style Examples\n\n'
130
+ echo "$EXAMPLES" | tr ',' '\n' | sed 's/^ */- /'
131
+ } > "${OUT_DIR}/brand-voice-guide.md"
132
+
133
+ echo "${OUT_DIR}/brand-voice-guide.md"
134
+ }
135
+
136
+ # =============================================================================
137
+ # Actions
138
+ # =============================================================================
139
+
140
+ case "$ACTION" in
141
+ start)
142
+ if [ -z "$TEAM_ID" ]; then
143
+ echo '{"success":false,"error":"teamId is required"}'
144
+ exit 1
145
+ fi
146
+
147
+ SESSION_ID="onboard-$(date +%s)-$RANDOM"
148
+ mkdir -p "$LOCAL_DIR"
149
+
150
+ # Create session file
151
+ jq -n \
152
+ --arg id "$SESSION_ID" \
153
+ --arg teamId "$TEAM_ID" \
154
+ --arg templateId "$TEMPLATE_ID" \
155
+ --argjson questions "$QUESTIONS" \
156
+ '{
157
+ id: $id,
158
+ teamId: $teamId,
159
+ templateId: $templateId,
160
+ status: "in_progress",
161
+ currentQuestionIndex: 0,
162
+ totalQuestions: ($questions | length),
163
+ answers: {},
164
+ createdAt: (now | todate)
165
+ }' > "${LOCAL_DIR}/${SESSION_ID}.json"
166
+
167
+ FIRST_Q=$(echo "$QUESTIONS" | jq -r '.[0].text')
168
+
169
+ echo "{\"success\":true,\"sessionId\":\"${SESSION_ID}\",\"status\":\"in_progress\",\"totalQuestions\":10,\"currentQuestion\":1,\"questionText\":\"${FIRST_Q}\"}"
170
+ ;;
171
+
172
+ question)
173
+ if [ -z "$SESSION_ID" ]; then
174
+ echo '{"success":false,"error":"sessionId is required"}'
175
+ exit 1
176
+ fi
177
+
178
+ SESSION_FILE="${LOCAL_DIR}/${SESSION_ID}.json"
179
+ if [ ! -f "$SESSION_FILE" ]; then
180
+ echo '{"success":false,"error":"Session not found"}'
181
+ exit 1
182
+ fi
183
+
184
+ IDX=$(jq -r '.currentQuestionIndex' "$SESSION_FILE")
185
+ TOTAL=$(echo "$QUESTIONS" | jq 'length')
186
+
187
+ if [ "$IDX" -ge "$TOTAL" ]; then
188
+ echo '{"success":true,"status":"completed","message":"All questions answered. Run action=complete to generate the Brand Voice Guide."}'
189
+ else
190
+ Q_TEXT=$(echo "$QUESTIONS" | jq -r ".[$IDX].text")
191
+ Q_ID=$(echo "$QUESTIONS" | jq -r ".[$IDX].id")
192
+ Q_REQ=$(echo "$QUESTIONS" | jq -r ".[$IDX].required")
193
+ echo "{\"success\":true,\"questionNumber\":$((IDX + 1)),\"totalQuestions\":${TOTAL},\"questionId\":\"${Q_ID}\",\"questionText\":\"${Q_TEXT}\",\"required\":${Q_REQ}}"
194
+ fi
195
+ ;;
196
+
197
+ answer)
198
+ if [ -z "$SESSION_ID" ]; then
199
+ echo '{"success":false,"error":"sessionId is required"}'
200
+ exit 1
201
+ fi
202
+
203
+ SESSION_FILE="${LOCAL_DIR}/${SESSION_ID}.json"
204
+ if [ ! -f "$SESSION_FILE" ]; then
205
+ echo '{"success":false,"error":"Session not found"}'
206
+ exit 1
207
+ fi
208
+
209
+ IDX=$(jq -r '.currentQuestionIndex' "$SESSION_FILE")
210
+ Q_ID=$(echo "$QUESTIONS" | jq -r ".[$IDX].id")
211
+ Q_REQ=$(echo "$QUESTIONS" | jq -r ".[$IDX].required")
212
+ TOTAL=$(echo "$QUESTIONS" | jq 'length')
213
+
214
+ # Validate required
215
+ if [ "$Q_REQ" = "true" ] && [ -z "$VALUE" ]; then
216
+ echo '{"success":false,"error":"This question requires an answer"}'
217
+ exit 1
218
+ fi
219
+
220
+ # Save answer and advance
221
+ jq --arg qid "$Q_ID" --arg val "$VALUE" \
222
+ '.answers[$qid] = $val | .currentQuestionIndex += 1' \
223
+ "$SESSION_FILE" > "${SESSION_FILE}.tmp" && mv "${SESSION_FILE}.tmp" "$SESSION_FILE"
224
+
225
+ NEXT_IDX=$((IDX + 1))
226
+ if [ "$NEXT_IDX" -ge "$TOTAL" ]; then
227
+ jq '.status = "completed"' "$SESSION_FILE" > "${SESSION_FILE}.tmp" && mv "${SESSION_FILE}.tmp" "$SESSION_FILE"
228
+ echo "{\"success\":true,\"status\":\"completed\",\"message\":\"All questions answered! Run action=complete to generate the Brand Voice Guide.\"}"
229
+ else
230
+ NEXT_Q=$(echo "$QUESTIONS" | jq -r ".[$NEXT_IDX].text")
231
+ echo "{\"success\":true,\"questionNumber\":$((NEXT_IDX + 1)),\"totalQuestions\":${TOTAL},\"nextQuestion\":\"${NEXT_Q}\"}"
232
+ fi
233
+ ;;
234
+
235
+ batch)
236
+ if [ -z "$TEAM_ID" ]; then
237
+ echo '{"success":false,"error":"teamId is required"}'
238
+ exit 1
239
+ fi
240
+
241
+ SESSION_ID="onboard-$(date +%s)-$RANDOM"
242
+ mkdir -p "$LOCAL_DIR"
243
+
244
+ # Extract answers from input
245
+ ANSWERS=$(echo "$INPUT" | jq '.answers // {}')
246
+
247
+ # Create completed session
248
+ echo "$ANSWERS" | jq --arg id "$SESSION_ID" --arg teamId "$TEAM_ID" \
249
+ '{
250
+ id: $id,
251
+ teamId: $teamId,
252
+ status: "completed",
253
+ currentQuestionIndex: 10,
254
+ totalQuestions: 10,
255
+ answers: .,
256
+ createdAt: (now | todate)
257
+ }' > "${LOCAL_DIR}/${SESSION_ID}.json"
258
+
259
+ # Auto-generate if outputDir provided
260
+ if [ -n "$OUTPUT_DIR" ]; then
261
+ ANSWERS_FILE="${LOCAL_DIR}/${SESSION_ID}-answers.json"
262
+ echo "$ANSWERS" > "$ANSWERS_FILE"
263
+ GUIDE_PATH=$(generate_guide "$ANSWERS_FILE" "$OUTPUT_DIR")
264
+ rm -f "$ANSWERS_FILE"
265
+ echo "{\"success\":true,\"sessionId\":\"${SESSION_ID}\",\"status\":\"done\",\"guidePath\":\"${GUIDE_PATH}\"}"
266
+ else
267
+ echo "{\"success\":true,\"sessionId\":\"${SESSION_ID}\",\"status\":\"completed\",\"message\":\"Answers saved. Run action=complete with outputDir to generate guide.\"}"
268
+ fi
269
+ ;;
270
+
271
+ complete)
272
+ if [ -z "$SESSION_ID" ]; then
273
+ echo '{"success":false,"error":"sessionId is required"}'
274
+ exit 1
275
+ fi
276
+ if [ -z "$OUTPUT_DIR" ]; then
277
+ echo '{"success":false,"error":"outputDir is required"}'
278
+ exit 1
279
+ fi
280
+
281
+ SESSION_FILE="${LOCAL_DIR}/${SESSION_ID}.json"
282
+ if [ ! -f "$SESSION_FILE" ]; then
283
+ echo '{"success":false,"error":"Session not found"}'
284
+ exit 1
285
+ fi
286
+
287
+ STATUS=$(jq -r '.status' "$SESSION_FILE")
288
+ if [ "$STATUS" != "completed" ]; then
289
+ echo '{"success":false,"error":"Session is not complete. Answer all questions first."}'
290
+ exit 1
291
+ fi
292
+
293
+ # Extract answers to temp file
294
+ ANSWERS_FILE="${LOCAL_DIR}/${SESSION_ID}-answers.json"
295
+ jq '.answers' "$SESSION_FILE" > "$ANSWERS_FILE"
296
+
297
+ # Generate guide
298
+ GUIDE_PATH=$(generate_guide "$ANSWERS_FILE" "$OUTPUT_DIR")
299
+ rm -f "$ANSWERS_FILE"
300
+
301
+ # Update session
302
+ jq --arg path "$GUIDE_PATH" '.status = "done" | .guidePath = $path' \
303
+ "$SESSION_FILE" > "${SESSION_FILE}.tmp" && mv "${SESSION_FILE}.tmp" "$SESSION_FILE"
304
+
305
+ echo "{\"success\":true,\"sessionId\":\"${SESSION_ID}\",\"status\":\"done\",\"guidePath\":\"${GUIDE_PATH}\",\"message\":\"Brand Voice Guide generated successfully.\"}"
306
+ ;;
307
+
308
+ *)
309
+ echo "{\"success\":false,\"error\":\"Unknown action: ${ACTION}. Use start, question, answer, batch, or complete.\"}"
310
+ exit 1
311
+ ;;
312
+ esac
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: submit-for-approval
3
+ description: Submit marketing content (posts, articles) for human approval via Slack. Used by Writer and Strategist agents in the Marketing Team template.
4
+ version: 1.0.0
5
+ category: marketing
6
+ assignableRoles:
7
+ - writer
8
+ - strategist
9
+ - content-writer
10
+ - content-strategist
11
+ triggers:
12
+ - before_publish
13
+ - manual
14
+ tags:
15
+ - marketing
16
+ - approval
17
+ - slack
18
+ - content
19
+ execution:
20
+ type: script
21
+ script: execute.sh
22
+ interpreter: bash
23
+ timeout: 30000
24
+ ---
25
+
26
+ # Submit for Approval
27
+
28
+ Submits marketing content for human review and approval via Slack.
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ bash execute.sh '{"action":"submit","platform":"Instagram","contentType":"post","content":"Your post text here...","hashtags":["#tag1","#tag2"],"visualDirection":"Photo of product","scheduledTime":"2026-03-25T09:00:00Z"}'
34
+ ```
35
+
36
+ ## Actions
37
+
38
+ ### submit
39
+ Submit content for approval. Sends a formatted message to the configured Slack channel.
40
+
41
+ **Parameters:**
42
+ - `platform` (required): Target platform (Instagram, X, LinkedIn, Facebook)
43
+ - `contentType` (required): Content type (post, article, newsletter, thread)
44
+ - `content` (required): The actual content text
45
+ - `hashtags` (optional): Array of hashtags
46
+ - `visualDirection` (optional): Visual/image brief
47
+ - `scheduledTime` (optional): When to publish
48
+
49
+ ### status
50
+ Check the status of a pending approval.
51
+
52
+ **Parameters:**
53
+ - `approvalId` (required): The approval request ID
54
+
55
+ ### list
56
+ List all pending approvals for the current team.
57
+
58
+ ## Response Format
59
+
60
+ ```json
61
+ {
62
+ "success": true,
63
+ "approvalId": "abc-123",
64
+ "status": "pending",
65
+ "message": "Content submitted for approval. The business owner will review via Slack."
66
+ }
67
+ ```
68
+
69
+ ## Integration
70
+
71
+ This skill integrates with the ContentApprovalService in the Crewly backend.
72
+ The approval message is sent to Slack where the business owner can reply
73
+ with "approve" or "reject [reason]" to resolve the request.
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env bash
2
+ # =============================================================================
3
+ # submit-for-approval — Submit marketing content for human approval
4
+ #
5
+ # Submits content to the ContentApprovalService via the Crewly API.
6
+ # The approval message is sent to the configured Slack channel.
7
+ #
8
+ # Usage:
9
+ # bash execute.sh '{"action":"submit","platform":"Instagram","contentType":"post","content":"...","hashtags":["#tag"]}'
10
+ # bash execute.sh '{"action":"status","approvalId":"abc-123"}'
11
+ # bash execute.sh '{"action":"list"}'
12
+ # =============================================================================
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT="${1:-"{}"}"
17
+
18
+ # Parse input fields
19
+ ACTION=$(echo "$INPUT" | jq -r '.action // "submit"')
20
+ PLATFORM=$(echo "$INPUT" | jq -r '.platform // ""')
21
+ CONTENT_TYPE=$(echo "$INPUT" | jq -r '.contentType // "post"')
22
+ CONTENT=$(echo "$INPUT" | jq -r '.content // ""')
23
+ HASHTAGS=$(echo "$INPUT" | jq -r '.hashtags // [] | join(", ")')
24
+ VISUAL_DIRECTION=$(echo "$INPUT" | jq -r '.visualDirection // ""')
25
+ SCHEDULED_TIME=$(echo "$INPUT" | jq -r '.scheduledTime // ""')
26
+ APPROVAL_ID=$(echo "$INPUT" | jq -r '.approvalId // ""')
27
+ TEAM_ID=$(echo "$INPUT" | jq -r '.teamId // ""')
28
+ PROJECT_PATH=$(echo "$INPUT" | jq -r '.projectPath // ""')
29
+
30
+ # Crewly API base URL
31
+ API_BASE="${CREWLY_API_URL:-http://localhost:3000}"
32
+
33
+ case "$ACTION" in
34
+ submit)
35
+ # Validate required fields
36
+ if [ -z "$PLATFORM" ]; then
37
+ echo '{"success":false,"error":"platform is required"}'
38
+ exit 1
39
+ fi
40
+ if [ -z "$CONTENT" ]; then
41
+ echo '{"success":false,"error":"content is required"}'
42
+ exit 1
43
+ fi
44
+
45
+ # Build the approval request payload
46
+ PAYLOAD=$(jq -n \
47
+ --arg teamId "$TEAM_ID" \
48
+ --arg submittedBy "${CREWLY_SESSION_NAME:-unknown}" \
49
+ --arg platform "$PLATFORM" \
50
+ --arg contentType "$CONTENT_TYPE" \
51
+ --arg content "$CONTENT" \
52
+ --arg hashtags "$HASHTAGS" \
53
+ --arg visualDirection "$VISUAL_DIRECTION" \
54
+ --arg scheduledTime "$SCHEDULED_TIME" \
55
+ '{
56
+ teamId: $teamId,
57
+ submittedBy: $submittedBy,
58
+ platform: $platform,
59
+ contentType: $contentType,
60
+ content: $content,
61
+ hashtags: (if $hashtags == "" then [] else ($hashtags | split(", ")) end),
62
+ visualDirection: (if $visualDirection == "" then null else $visualDirection end),
63
+ scheduledTime: (if $scheduledTime == "" then null else $scheduledTime end)
64
+ }')
65
+
66
+ # Submit via API
67
+ RESPONSE=$(curl -s -X POST "${API_BASE}/api/content-approvals" \
68
+ -H "Content-Type: application/json" \
69
+ -d "$PAYLOAD" 2>/dev/null || echo '{"error":"API unreachable"}')
70
+
71
+ # Check if API call succeeded
72
+ if echo "$RESPONSE" | jq -e '.id' >/dev/null 2>&1; then
73
+ APPROVAL_ID=$(echo "$RESPONSE" | jq -r '.id')
74
+ echo "{\"success\":true,\"approvalId\":\"${APPROVAL_ID}\",\"status\":\"pending\",\"message\":\"Content submitted for approval. The business owner will review via Slack.\"}"
75
+ else
76
+ # Fallback: store locally if API is not available
77
+ APPROVAL_ID="local-$(date +%s)-$RANDOM"
78
+ APPROVAL_DIR="${PROJECT_PATH:-.}/.crewly/content-approvals"
79
+ mkdir -p "$APPROVAL_DIR"
80
+
81
+ echo "$PAYLOAD" | jq --arg id "$APPROVAL_ID" --arg status "pending" \
82
+ '. + {id: $id, status: $status, submittedAt: (now | todate)}' \
83
+ > "${APPROVAL_DIR}/${APPROVAL_ID}.json"
84
+
85
+ echo "{\"success\":true,\"approvalId\":\"${APPROVAL_ID}\",\"status\":\"pending\",\"message\":\"Content saved locally for approval (API not available). File: ${APPROVAL_DIR}/${APPROVAL_ID}.json\"}"
86
+ fi
87
+ ;;
88
+
89
+ status)
90
+ if [ -z "$APPROVAL_ID" ]; then
91
+ echo '{"success":false,"error":"approvalId is required"}'
92
+ exit 1
93
+ fi
94
+
95
+ # Check via API
96
+ RESPONSE=$(curl -s "${API_BASE}/api/content-approvals/${APPROVAL_ID}" 2>/dev/null || echo '{"error":"API unreachable"}')
97
+
98
+ if echo "$RESPONSE" | jq -e '.id' >/dev/null 2>&1; then
99
+ STATUS=$(echo "$RESPONSE" | jq -r '.status')
100
+ FEEDBACK=$(echo "$RESPONSE" | jq -r '.feedback // "none"')
101
+ echo "{\"success\":true,\"approvalId\":\"${APPROVAL_ID}\",\"status\":\"${STATUS}\",\"feedback\":\"${FEEDBACK}\"}"
102
+ else
103
+ # Fallback: check local file
104
+ APPROVAL_DIR="${PROJECT_PATH:-.}/.crewly/content-approvals"
105
+ LOCAL_FILE="${APPROVAL_DIR}/${APPROVAL_ID}.json"
106
+ if [ -f "$LOCAL_FILE" ]; then
107
+ STATUS=$(jq -r '.status' "$LOCAL_FILE")
108
+ echo "{\"success\":true,\"approvalId\":\"${APPROVAL_ID}\",\"status\":\"${STATUS}\",\"source\":\"local\"}"
109
+ else
110
+ echo "{\"success\":false,\"error\":\"Approval ${APPROVAL_ID} not found\"}"
111
+ fi
112
+ fi
113
+ ;;
114
+
115
+ list)
116
+ # List pending approvals via API
117
+ RESPONSE=$(curl -s "${API_BASE}/api/content-approvals?teamId=${TEAM_ID}&status=pending" 2>/dev/null || echo '[]')
118
+
119
+ if echo "$RESPONSE" | jq -e '.[0]' >/dev/null 2>&1; then
120
+ COUNT=$(echo "$RESPONSE" | jq 'length')
121
+ echo "{\"success\":true,\"count\":${COUNT},\"approvals\":${RESPONSE}}"
122
+ else
123
+ # Fallback: list local files
124
+ APPROVAL_DIR="${PROJECT_PATH:-.}/.crewly/content-approvals"
125
+ if [ -d "$APPROVAL_DIR" ]; then
126
+ COUNT=$(ls -1 "$APPROVAL_DIR"/*.json 2>/dev/null | wc -l | tr -d ' ')
127
+ echo "{\"success\":true,\"count\":${COUNT},\"source\":\"local\",\"directory\":\"${APPROVAL_DIR}\"}"
128
+ else
129
+ echo "{\"success\":true,\"count\":0,\"approvals\":[]}"
130
+ fi
131
+ fi
132
+ ;;
133
+
134
+ *)
135
+ echo "{\"success\":false,\"error\":\"Unknown action: ${ACTION}. Use submit, status, or list.\"}"
136
+ exit 1
137
+ ;;
138
+ esac
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: weekly-content-planning
3
+ description: Generates a weekly content calendar with 5-7 posts across specified platforms. Wraps the content-calendar skill to create batch entries for the upcoming week.
4
+ version: 1.0.0
5
+ category: marketing
6
+ skillType: claude-skill
7
+ assignableRoles:
8
+ - strategist
9
+ triggers:
10
+ - weekly content plan
11
+ - plan next week
12
+ - content planning
13
+ - weekly calendar
14
+ tags:
15
+ - marketing
16
+ - content
17
+ - planning
18
+ - calendar
19
+ - weekly
20
+ execution:
21
+ type: script
22
+ script:
23
+ file: execute.sh
24
+ interpreter: bash
25
+ timeoutMs: 30000
26
+ ---
27
+
28
+ # Weekly Content Planning
29
+
30
+ Generates a weekly content calendar with 5-7 posts across specified platforms. Uses the content-calendar skill to persist each entry.
31
+
32
+ ## Input Parameters
33
+
34
+ | Parameter | Required | Description |
35
+ |-----------|----------|-------------|
36
+ | `businessName` | Yes | Name of the business |
37
+ | `industry` | Yes | Business industry/vertical |
38
+ | `platforms` | Yes | JSON array of target platforms (e.g., `["x","linkedin"]`) |
39
+ | `contentMix` | No | JSON object with content type ratios (default: `{"educational":40,"engagement":30,"promotional":20,"community":10}`) |
40
+ | `weekStartDate` | Yes | Start date of the week in `YYYY-MM-DD` format |
41
+ | `projectPath` | No | Project path for calendar storage |
42
+ | `postCount` | No | Number of posts to plan (default: 5) |
43
+
44
+ ## Example
45
+
46
+ ```bash
47
+ bash execute.sh '{"businessName":"Acme AI","industry":"artificial intelligence","platforms":["x","linkedin","instagram"],"weekStartDate":"2026-03-23","projectPath":"/path/to/project"}'
48
+ ```
49
+
50
+ ## Output
51
+
52
+ Returns JSON with `success: true` and a `calendar` field containing the generated entries as a markdown table, plus an `entries` array with the raw entry data.