crewly 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +6 -6
  2. package/config/roles/ops/prompt.md +140 -0
  3. package/config/roles/ops/role.json +13 -0
  4. package/config/skills/agent/browse-stealth/execute.sh +84 -0
  5. package/config/skills/agent/browse-stealth/instructions.md +108 -0
  6. package/config/skills/agent/browse-stealth/launch-chrome-cdp.sh +141 -0
  7. package/config/skills/agent/browse-stealth/skill.json +20 -0
  8. package/config/skills/agent/browse-stealth/stealth-browse.py +330 -0
  9. package/config/skills/agent/competitor-content-tracker/execute.sh +232 -0
  10. package/config/skills/agent/competitor-content-tracker/instructions.md +210 -0
  11. package/config/skills/agent/competitor-content-tracker/skill.json +22 -0
  12. package/config/skills/agent/content-calendar/execute.sh +294 -0
  13. package/config/skills/agent/content-calendar/instructions.md +122 -0
  14. package/config/skills/agent/content-calendar/skill.json +22 -0
  15. package/config/skills/agent/content-repurposer/execute.sh +194 -0
  16. package/config/skills/agent/content-repurposer/instructions.md +69 -0
  17. package/config/skills/agent/content-repurposer/skill.json +22 -0
  18. package/config/skills/agent/content-writer/execute.sh +311 -0
  19. package/config/skills/agent/content-writer/instructions.md +124 -0
  20. package/config/skills/agent/content-writer/skill.json +22 -0
  21. package/config/skills/agent/core/generate-pdf/execute.sh +88 -0
  22. package/config/skills/agent/core/generate-pdf/instructions.md +46 -0
  23. package/config/skills/agent/core/generate-pdf/skill.json +20 -0
  24. package/config/skills/agent/core/report-status/execute.sh +6 -0
  25. package/config/skills/agent/trend-monitor/execute.sh +211 -0
  26. package/config/skills/agent/trend-monitor/instructions.md +207 -0
  27. package/config/skills/agent/trend-monitor/skill.json +22 -0
  28. package/config/skills/agent/vnc-browser/execute.sh +261 -0
  29. package/config/skills/agent/vnc-browser/instructions.md +102 -0
  30. package/config/skills/agent/vnc-browser/skill.json +20 -0
  31. package/config/skills/orchestrator/delegate-task/execute.sh +63 -4
  32. package/config/skills/orchestrator/delegate-task/instructions.md +60 -0
  33. package/config/skills/orchestrator/delegate-task/skill.json +4 -4
  34. package/config/skills/orchestrator/reply-slack/execute.sh +2 -0
  35. package/config/skills/orchestrator/send-key/execute.sh +19 -6
  36. package/config/skills/orchestrator/send-key/instructions.md +44 -0
  37. package/config/skills/orchestrator/send-key/skill.json +20 -0
  38. package/config/skills/orchestrator/send-message/execute.sh +9 -1
  39. package/config/skills/registry.json +256 -0
  40. package/config/templates/code-review-team/README.md +176 -0
  41. package/config/templates/code-review-team/team-config.json +16 -0
  42. package/config/templates/code-review-team.json +62 -0
  43. package/config/templates/content-generation-team/README.md +128 -0
  44. package/config/templates/content-generation-team/team-config.json +21 -0
  45. package/config/templates/content-generation-team.json +67 -0
  46. package/config/templates/demo-team.json +22 -0
  47. package/config/templates/social-media-ops-team/README.md +145 -0
  48. package/config/templates/social-media-ops-team/team-config.json +21 -0
  49. package/config/templates/social-media-ops-team.json +67 -0
  50. package/dist/backend/backend/src/constants.d.ts +69 -6
  51. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  52. package/dist/backend/backend/src/constants.js +75 -6
  53. package/dist/backend/backend/src/constants.js.map +1 -1
  54. package/dist/backend/backend/src/controllers/index.d.ts.map +1 -1
  55. package/dist/backend/backend/src/controllers/index.js +2 -0
  56. package/dist/backend/backend/src/controllers/index.js.map +1 -1
  57. package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts +8 -0
  58. package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts.map +1 -1
  59. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js +110 -63
  60. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js.map +1 -1
  61. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
  62. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +31 -4
  63. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
  64. package/dist/backend/backend/src/controllers/oauth/oauth.routes.d.ts +8 -0
  65. package/dist/backend/backend/src/controllers/oauth/oauth.routes.d.ts.map +1 -1
  66. package/dist/backend/backend/src/controllers/oauth/oauth.routes.js +127 -111
  67. package/dist/backend/backend/src/controllers/oauth/oauth.routes.js.map +1 -1
  68. package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts +34 -0
  69. package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts.map +1 -1
  70. package/dist/backend/backend/src/controllers/task-management/task-management.controller.js +219 -2
  71. package/dist/backend/backend/src/controllers/task-management/task-management.controller.js.map +1 -1
  72. package/dist/backend/backend/src/controllers/user/user.routes.d.ts +7 -0
  73. package/dist/backend/backend/src/controllers/user/user.routes.d.ts.map +1 -1
  74. package/dist/backend/backend/src/controllers/user/user.routes.js +45 -38
  75. package/dist/backend/backend/src/controllers/user/user.routes.js.map +1 -1
  76. package/dist/backend/backend/src/controllers/whatsapp/index.d.ts +17 -0
  77. package/dist/backend/backend/src/controllers/whatsapp/index.d.ts.map +1 -0
  78. package/dist/backend/backend/src/controllers/whatsapp/index.js +18 -0
  79. package/dist/backend/backend/src/controllers/whatsapp/index.js.map +1 -0
  80. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts +12 -0
  81. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts.map +1 -0
  82. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js +185 -0
  83. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js.map +1 -0
  84. package/dist/backend/backend/src/index.d.ts +5 -0
  85. package/dist/backend/backend/src/index.d.ts.map +1 -1
  86. package/dist/backend/backend/src/index.js +35 -0
  87. package/dist/backend/backend/src/index.js.map +1 -1
  88. package/dist/backend/backend/src/routes/modules/task-management.routes.d.ts.map +1 -1
  89. package/dist/backend/backend/src/routes/modules/task-management.routes.js +4 -0
  90. package/dist/backend/backend/src/routes/modules/task-management.routes.js.map +1 -1
  91. package/dist/backend/backend/src/services/agent/agent-heartbeat.service.js +1 -1
  92. package/dist/backend/backend/src/services/agent/agent-heartbeat.service.js.map +1 -1
  93. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +14 -3
  94. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  95. package/dist/backend/backend/src/services/agent/agent-registration.service.js +160 -29
  96. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  97. package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts +4 -3
  98. package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -1
  99. package/dist/backend/backend/src/services/agent/claude-runtime.service.js +29 -4
  100. package/dist/backend/backend/src/services/agent/claude-runtime.service.js.map +1 -1
  101. package/dist/backend/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -1
  102. package/dist/backend/backend/src/services/agent/context-window-monitor.service.js +11 -0
  103. package/dist/backend/backend/src/services/agent/context-window-monitor.service.js.map +1 -1
  104. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +32 -2
  105. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  106. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +69 -8
  107. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  108. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
  109. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +14 -2
  110. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
  111. package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -1
  112. package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js +11 -2
  113. package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -1
  114. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.d.ts +18 -0
  115. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.d.ts.map +1 -1
  116. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.js +28 -4
  117. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.js.map +1 -1
  118. package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.js +2 -2
  119. package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.js.map +1 -1
  120. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.d.ts +18 -0
  121. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.d.ts.map +1 -1
  122. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.js +26 -4
  123. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.js.map +1 -1
  124. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts +28 -2
  125. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts.map +1 -1
  126. package/dist/backend/backend/src/services/messaging/messenger-registry.service.d.ts +33 -2
  127. package/dist/backend/backend/src/services/messaging/messenger-registry.service.d.ts.map +1 -1
  128. package/dist/backend/backend/src/services/messaging/messenger-registry.service.js +33 -0
  129. package/dist/backend/backend/src/services/messaging/messenger-registry.service.js.map +1 -1
  130. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
  131. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js +4 -2
  132. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
  133. package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.d.ts.map +1 -1
  134. package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js +4 -3
  135. package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js.map +1 -1
  136. package/dist/backend/backend/src/services/project/task-tracking.service.d.ts +27 -0
  137. package/dist/backend/backend/src/services/project/task-tracking.service.d.ts.map +1 -1
  138. package/dist/backend/backend/src/services/project/task-tracking.service.js +54 -0
  139. package/dist/backend/backend/src/services/project/task-tracking.service.js.map +1 -1
  140. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +36 -6
  141. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  142. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +238 -36
  143. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  144. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  145. package/dist/backend/backend/src/services/slack/slack.service.js +6 -4
  146. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  147. package/dist/backend/backend/src/services/user/user-identity.service.d.ts +44 -0
  148. package/dist/backend/backend/src/services/user/user-identity.service.d.ts.map +1 -1
  149. package/dist/backend/backend/src/services/user/user-identity.service.js +75 -8
  150. package/dist/backend/backend/src/services/user/user-identity.service.js.map +1 -1
  151. package/dist/backend/backend/src/services/whatsapp/index.d.ts +11 -0
  152. package/dist/backend/backend/src/services/whatsapp/index.d.ts.map +1 -0
  153. package/dist/backend/backend/src/services/whatsapp/index.js +11 -0
  154. package/dist/backend/backend/src/services/whatsapp/index.js.map +1 -0
  155. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts +66 -0
  156. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts.map +1 -0
  157. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js +96 -0
  158. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js.map +1 -0
  159. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts +109 -0
  160. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -0
  161. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +234 -0
  162. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -0
  163. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts +127 -0
  164. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts.map +1 -0
  165. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js +347 -0
  166. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js.map +1 -0
  167. package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts.map +1 -1
  168. package/dist/backend/backend/src/services/workflow/scheduler.service.js +4 -0
  169. package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -1
  170. package/dist/backend/backend/src/types/index.d.ts +1 -0
  171. package/dist/backend/backend/src/types/index.d.ts.map +1 -1
  172. package/dist/backend/backend/src/types/index.js.map +1 -1
  173. package/dist/backend/backend/src/types/slack.types.d.ts +24 -0
  174. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  175. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  176. package/dist/backend/backend/src/types/task-tracking.types.d.ts +4 -0
  177. package/dist/backend/backend/src/types/task-tracking.types.d.ts.map +1 -1
  178. package/dist/backend/backend/src/types/task-tracking.types.js.map +1 -1
  179. package/dist/backend/backend/src/types/whatsapp.types.d.ts +84 -0
  180. package/dist/backend/backend/src/types/whatsapp.types.d.ts.map +1 -0
  181. package/dist/backend/backend/src/types/whatsapp.types.js +33 -0
  182. package/dist/backend/backend/src/types/whatsapp.types.js.map +1 -0
  183. package/dist/backend/backend/src/websocket/terminal.gateway.d.ts +11 -0
  184. package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
  185. package/dist/backend/backend/src/websocket/terminal.gateway.js +35 -1
  186. package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
  187. package/dist/cli/backend/src/constants.d.ts +69 -6
  188. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  189. package/dist/cli/backend/src/constants.js +75 -6
  190. package/dist/cli/backend/src/constants.js.map +1 -1
  191. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
  192. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js +14 -2
  193. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
  194. package/dist/cli/backend/src/types/index.d.ts +1 -0
  195. package/dist/cli/backend/src/types/index.d.ts.map +1 -1
  196. package/dist/cli/backend/src/types/index.js.map +1 -1
  197. package/dist/cli/cli/src/commands/publish.d.ts.map +1 -1
  198. package/dist/cli/cli/src/commands/publish.js +17 -15
  199. package/dist/cli/cli/src/commands/publish.js.map +1 -1
  200. package/dist/cli/cli/src/index.js +2 -2
  201. package/dist/cli/cli/src/index.js.map +1 -1
  202. package/dist/cli/cli/src/utils/gh-submit.d.ts +46 -0
  203. package/dist/cli/cli/src/utils/gh-submit.d.ts.map +1 -0
  204. package/dist/cli/cli/src/utils/gh-submit.js +167 -0
  205. package/dist/cli/cli/src/utils/gh-submit.js.map +1 -0
  206. package/dist/cli/cli/src/utils/marketplace.d.ts.map +1 -1
  207. package/dist/cli/cli/src/utils/marketplace.js +13 -5
  208. package/dist/cli/cli/src/utils/marketplace.js.map +1 -1
  209. package/dist/cli/cli/src/utils/templates.d.ts +3 -2
  210. package/dist/cli/cli/src/utils/templates.d.ts.map +1 -1
  211. package/dist/cli/cli/src/utils/templates.js +5 -4
  212. package/dist/cli/cli/src/utils/templates.js.map +1 -1
  213. package/frontend/dist/assets/{index-45eeea99.js → index-a23214ae.js} +241 -241
  214. package/frontend/dist/assets/{index-6972eeee.css → index-c407fe13.css} +1 -1
  215. package/frontend/dist/index.html +2 -2
  216. package/package.json +3 -1
@@ -0,0 +1,194 @@
1
+ #!/bin/bash
2
+ # Repurpose source content into platform-optimized versions
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 '{\"source\":\"content text\",\"platforms\":\"x-thread,linkedin,xiaohongshu\",\"tone\":\"professional\",\"brand\":\"personal\",\"outputDir\":\"/path/to/output\"}'"
9
+
10
+ # Parse parameters
11
+ SOURCE=$(echo "$INPUT" | jq -r '.source // empty')
12
+ SOURCE_FILE=$(echo "$INPUT" | jq -r '.sourceFile // empty')
13
+ PLATFORMS=$(echo "$INPUT" | jq -r '.platforms // "x-thread,linkedin"')
14
+ TONE=$(echo "$INPUT" | jq -r '.tone // "professional"')
15
+ OUTPUT_DIR=$(echo "$INPUT" | jq -r '.outputDir // empty')
16
+ BRAND=$(echo "$INPUT" | jq -r '.brand // "personal"')
17
+ LANGUAGE=$(echo "$INPUT" | jq -r '.language // "auto"')
18
+
19
+ # Load source content from file if sourceFile is provided
20
+ if [ -n "$SOURCE_FILE" ]; then
21
+ if [ ! -f "$SOURCE_FILE" ]; then
22
+ error_exit "Source file not found: $SOURCE_FILE"
23
+ fi
24
+ SOURCE=$(cat "$SOURCE_FILE")
25
+ fi
26
+
27
+ require_param "source" "$SOURCE"
28
+
29
+ # Truncate source for processing (keep first 4000 chars)
30
+ if [ ${#SOURCE} -gt 4000 ]; then
31
+ SOURCE="${SOURCE:0:4000}..."
32
+ fi
33
+
34
+ # --- Platform config lookup functions (bash 3.x compatible) ---
35
+
36
+ get_platform_max_len() {
37
+ case "$1" in
38
+ x-thread) echo "1400" ;;
39
+ x-single) echo "280" ;;
40
+ linkedin) echo "3000" ;;
41
+ xiaohongshu) echo "1000" ;;
42
+ substack) echo "10000" ;;
43
+ youtube-desc) echo "5000" ;;
44
+ *) echo "" ;;
45
+ esac
46
+ }
47
+
48
+ get_platform_style() {
49
+ case "$1" in
50
+ x-thread)
51
+ echo "Split into 3-7 tweets (each <=280 chars). Start with a strong hook. Use line breaks between tweets. Mark tweet boundaries with [1/N] format. Include 2-3 relevant hashtags on the last tweet only. Conversational, punchy, opinionated." ;;
52
+ x-single)
53
+ echo "Single tweet, max 280 chars. Punchy hook + key insight. 1-2 hashtags max." ;;
54
+ linkedin)
55
+ echo "Professional long-form post, 150-300 words. Start with a bold opening line. Use short paragraphs (1-2 sentences each). Include a clear takeaway or call-to-action. Data-driven where possible. No hashtags in the body, add 3-5 hashtags at the very end." ;;
56
+ xiaohongshu)
57
+ echo "Chinese language (Simplified). Casual, relatable tone with emoji. Start with an attention-grabbing title line. Use bullet points and short paragraphs. Include 5-8 topic hashtags in #topic# format at the end. Target audience: Chinese tech professionals, entrepreneurs, AI enthusiasts." ;;
58
+ substack)
59
+ echo "Long-form article/newsletter format. Include a compelling title, subtitle, and structured sections with H2/H3 headers. Write 500-1500 words expanding on the source. Include a personal anecdote or insight. End with a discussion prompt for reader engagement." ;;
60
+ youtube-desc)
61
+ echo "YouTube video description format. Start with a 2-3 sentence summary. Include timestamps placeholder [00:00 - Topic]. Add relevant links section. Include 10-15 tags/keywords comma-separated at the bottom. Keep under 5000 chars." ;;
62
+ *)
63
+ echo "" ;;
64
+ esac
65
+ }
66
+
67
+ get_platform_suffix() {
68
+ case "$1" in
69
+ x-thread) echo "x-thread.md" ;;
70
+ x-single) echo "x-single.md" ;;
71
+ linkedin) echo "linkedin.md" ;;
72
+ xiaohongshu) echo "xiaohongshu.md" ;;
73
+ substack) echo "substack.md" ;;
74
+ youtube-desc) echo "youtube-desc.md" ;;
75
+ *) echo "" ;;
76
+ esac
77
+ }
78
+
79
+ get_tone_desc() {
80
+ case "$1" in
81
+ professional) echo "Professional and authoritative. Data-driven. Measured confidence." ;;
82
+ casual) echo "Conversational and approachable. Uses humor sparingly. Feels like talking to a smart friend." ;;
83
+ technical) echo "Precise and detailed. Assumes technical audience. Uses specific terminology." ;;
84
+ inspiring) echo "Motivational and forward-looking. Personal stories. Empowering language." ;;
85
+ provocative) echo "Bold and contrarian. Challenges conventional wisdom. Strong opinions backed by reasoning." ;;
86
+ *) echo "Professional and authoritative. Data-driven. Measured confidence." ;;
87
+ esac
88
+ }
89
+
90
+ get_brand_voice() {
91
+ case "$1" in
92
+ personal) echo "Steve's personal voice: Google PM with 7 years experience, side hustle enthusiast (24 projects), AI-native builder, Build in Public advocate. Speaks from first-person experience. Boston-based, bilingual (EN/CN)." ;;
93
+ crewly) echo "Crewly brand voice: Technical but not boring. Developer-first. Concise and powerful. Honest about limitations. Uses concrete examples and code snippets. Tagline: Your AI Team, Ready in Days - Not Months." ;;
94
+ *) echo "Professional voice with authentic personal touch." ;;
95
+ esac
96
+ }
97
+
98
+ # --- Resolve tone and brand ---
99
+ TONE_MODIFIER=$(get_tone_desc "$TONE")
100
+ BRAND_MODIFIER=$(get_brand_voice "$BRAND")
101
+
102
+ # Parse platforms
103
+ IFS=',' read -ra PLATFORM_ARRAY <<< "$PLATFORMS"
104
+
105
+ RESULTS="[]"
106
+ FILES_WRITTEN="[]"
107
+
108
+ for platform in "${PLATFORM_ARRAY[@]}"; do
109
+ platform=$(echo "$platform" | xargs | tr '[:upper:]' '[:lower:]')
110
+
111
+ MAX_LEN=$(get_platform_max_len "$platform")
112
+ STYLE=$(get_platform_style "$platform")
113
+ SUFFIX=$(get_platform_suffix "$platform")
114
+
115
+ if [ -z "$STYLE" ]; then
116
+ RESULTS=$(echo "$RESULTS" | jq \
117
+ --arg platform "$platform" \
118
+ '. + [{"platform":$platform,"error":"Unsupported platform. Use: x-thread, x-single, linkedin, xiaohongshu, substack, youtube-desc"}]')
119
+ continue
120
+ fi
121
+
122
+ # Language instruction
123
+ LANG_INSTRUCTION=""
124
+ if [ "$LANGUAGE" = "auto" ]; then
125
+ if [ "$platform" = "xiaohongshu" ]; then
126
+ LANG_INSTRUCTION="Write in Simplified Chinese."
127
+ else
128
+ LANG_INSTRUCTION="Write in English."
129
+ fi
130
+ elif [ "$LANGUAGE" = "zh" ]; then
131
+ LANG_INSTRUCTION="Write in Simplified Chinese."
132
+ elif [ "$LANGUAGE" = "en" ]; then
133
+ LANG_INSTRUCTION="Write in English."
134
+ elif [ "$LANGUAGE" = "both" ]; then
135
+ LANG_INSTRUCTION="Write in English with key phrases in Chinese where natural."
136
+ fi
137
+
138
+ # Write prompt file if outputDir specified
139
+ FILE_PATH=""
140
+ if [ -n "$OUTPUT_DIR" ]; then
141
+ mkdir -p "$OUTPUT_DIR"
142
+ FILE_PATH="${OUTPUT_DIR}/${SUFFIX}"
143
+ cat > "$FILE_PATH" <<PROMPT_EOF
144
+ --- PLATFORM: ${platform} ---
145
+ MAX_LENGTH: ${MAX_LEN} chars
146
+ STYLE: ${STYLE}
147
+ TONE: ${TONE_MODIFIER}
148
+ BRAND: ${BRAND_MODIFIER}
149
+ ${LANG_INSTRUCTION}
150
+ ---
151
+
152
+ SOURCE CONTENT TO REPURPOSE:
153
+ ${SOURCE}
154
+ PROMPT_EOF
155
+ FILES_WRITTEN=$(echo "$FILES_WRITTEN" | jq --arg fp "$FILE_PATH" '. + [$fp]')
156
+ fi
157
+
158
+ RESULTS=$(echo "$RESULTS" | jq \
159
+ --arg platform "$platform" \
160
+ --arg maxLen "$MAX_LEN" \
161
+ --arg style "$STYLE" \
162
+ --arg tone "$TONE_MODIFIER" \
163
+ --arg brand "$BRAND_MODIFIER" \
164
+ --arg lang "$LANG_INSTRUCTION" \
165
+ --arg filePath "$FILE_PATH" \
166
+ '. + [{
167
+ "platform": $platform,
168
+ "maxLength": ($maxLen | tonumber),
169
+ "styleGuide": $style,
170
+ "toneGuide": $tone,
171
+ "brandVoice": $brand,
172
+ "language": $lang,
173
+ "outputFile": $filePath
174
+ }]')
175
+ done
176
+
177
+ # Build output
178
+ OUTPUT=$(jq -n \
179
+ --arg sourceLength "${#SOURCE}" \
180
+ --arg tone "$TONE" \
181
+ --arg brand "$BRAND" \
182
+ --argjson platforms "$RESULTS" \
183
+ --argjson filesWritten "$FILES_WRITTEN" \
184
+ '{
185
+ "success": true,
186
+ "sourceContentLength": ($sourceLength | tonumber),
187
+ "tone": $tone,
188
+ "brand": $brand,
189
+ "platforms": $platforms,
190
+ "filesWritten": $filesWritten,
191
+ "instructions": "Each platform entry contains the style guide, tone, brand voice, and language instructions. Use these as system prompts when generating the actual repurposed content with an LLM. If outputDir was specified, prompt files have been written to disk."
192
+ }')
193
+
194
+ echo "$OUTPUT"
@@ -0,0 +1,69 @@
1
+ # Content Repurposer
2
+
3
+ Repurpose a single piece of source content into platform-optimized versions. Supports X (thread and single tweet), LinkedIn, Xiaohongshu (RedNote), Substack, and YouTube descriptions.
4
+
5
+ ## How It Works
6
+
7
+ This skill does NOT generate the final content directly. Instead, it:
8
+ 1. Takes your source content and target platforms
9
+ 2. Outputs platform-specific **style guides, tone modifiers, and brand voice instructions**
10
+ 3. You (the agent) then use these guides as context to generate the actual repurposed content
11
+
12
+ This design keeps the LLM-powered content generation in the agent's hands (where quality control happens) while the skill handles the structured platform rules.
13
+
14
+ ## Parameters
15
+
16
+ | Parameter | Required | Description |
17
+ |-----------|----------|-------------|
18
+ | `source` | Yes* | The source content text to repurpose (inline) |
19
+ | `sourceFile` | Yes* | Path to a markdown file containing the source content |
20
+ | `platforms` | No | Comma-separated target platforms (default: `x-thread,linkedin`) |
21
+ | `tone` | No | `professional`, `casual`, `technical`, `inspiring`, `provocative` (default: `professional`) |
22
+ | `brand` | No | `personal` (Steve's voice) or `crewly` (Crewly brand voice). Default: `personal` |
23
+ | `language` | No | `auto` (xiaohongshu=zh, others=en), `en`, `zh`, or `both`. Default: `auto` |
24
+ | `outputDir` | No | Directory to write prompt files (one per platform). If omitted, output is JSON only |
25
+
26
+ *One of `source` or `sourceFile` is required.
27
+
28
+ ## Supported Platforms
29
+
30
+ | Platform | Key | Max Length | Format |
31
+ |----------|-----|-----------|--------|
32
+ | X Thread | `x-thread` | ~1400 chars (5 tweets) | 3-7 tweets with [1/N] markers |
33
+ | X Single | `x-single` | 280 chars | Single punchy tweet |
34
+ | LinkedIn | `linkedin` | 3000 chars | Professional long-form post |
35
+ | Xiaohongshu | `xiaohongshu` | 1000 chars | Chinese, emoji-rich, #话题# tags |
36
+ | Substack | `substack` | 10000 chars | Newsletter/article with sections |
37
+ | YouTube Desc | `youtube-desc` | 5000 chars | Description with timestamps, links, tags |
38
+
39
+ ## Examples
40
+
41
+ ### Basic: Blog post to X thread + LinkedIn
42
+ ```bash
43
+ bash execute.sh '{"source":"Today I learned that AI agents can coordinate across multiple tasks...","platforms":"x-thread,linkedin","tone":"casual","brand":"personal"}'
44
+ ```
45
+
46
+ ### From file to all platforms
47
+ ```bash
48
+ bash execute.sh '{"sourceFile":"/path/to/blog-post.md","platforms":"x-thread,linkedin,xiaohongshu,substack","tone":"professional","brand":"crewly","outputDir":"/tmp/repurposed"}'
49
+ ```
50
+
51
+ ### Steve's personal content to Xiaohongshu
52
+ ```bash
53
+ bash execute.sh '{"source":"My AI team shipped 3 features while I slept...","platforms":"xiaohongshu","tone":"casual","brand":"personal","language":"zh"}'
54
+ ```
55
+
56
+ ## Output
57
+
58
+ JSON object containing:
59
+ - `platforms[]` — Array of platform configs, each with `styleGuide`, `toneGuide`, `brandVoice`, `language`, `maxLength`
60
+ - `filesWritten[]` — Paths to written prompt files (if `outputDir` was specified)
61
+ - `instructions` — How to use the output
62
+
63
+ ## Workflow
64
+
65
+ 1. Run this skill to get platform-specific guides
66
+ 2. For each platform, use the guide as context to generate content with the LLM
67
+ 3. Review and adjust the generated content
68
+ 4. Use `content-calendar` skill to schedule the content
69
+ 5. Publish (manually or via platform-specific publisher skill)
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "content-repurposer",
3
+ "name": "Content Repurposer",
4
+ "description": "Repurpose source content into platform-optimized versions for X (thread/single), LinkedIn, Xiaohongshu, Substack, and YouTube descriptions. Provides platform-specific style guides, tone modifiers, and brand voice presets.",
5
+ "category": "content",
6
+ "skillType": "claude-skill",
7
+ "promptFile": "instructions.md",
8
+ "execution": {
9
+ "type": "script",
10
+ "script": {
11
+ "file": "execute.sh",
12
+ "interpreter": "bash",
13
+ "timeoutMs": 15000
14
+ }
15
+ },
16
+ "assignableRoles": ["content-strategist", "product-manager", "generalist", "designer", "sales"],
17
+ "triggers": ["repurpose content", "adapt content", "cross-post", "multi-platform content", "convert to thread", "convert to linkedin"],
18
+ "tags": ["content", "repurpose", "multi-platform", "social-media", "marketing", "x-thread", "linkedin", "xiaohongshu", "substack", "youtube"],
19
+ "version": "1.0.0",
20
+ "author": "Luna (Content Strategist)",
21
+ "license": "MIT"
22
+ }
@@ -0,0 +1,311 @@
1
+ #!/bin/bash
2
+ # Content Writer — generate and manage content drafts
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 '{\"action\":\"draft|save|get|list\",\"topic\":\"...\",\"platform\":\"x-thread\",...}'"
9
+
10
+ ACTION=$(echo "$INPUT" | jq -r '.action // "draft"')
11
+ PROJECT_PATH=$(echo "$INPUT" | jq -r '.projectPath // empty')
12
+
13
+ # Resolve content directory
14
+ if [ -n "$PROJECT_PATH" ]; then
15
+ CONTENT_DIR="${PROJECT_PATH}/.crewly/content/drafts"
16
+ else
17
+ CONTENT_DIR="${HOME}/.crewly/content/drafts"
18
+ fi
19
+ mkdir -p "$CONTENT_DIR"
20
+
21
+ case "$ACTION" in
22
+
23
+ # ─────────────────────────────────────────────
24
+ # DRAFT: Generate a writing brief / prompt for a content piece
25
+ # ─────────────────────────────────────────────
26
+ draft)
27
+ TOPIC=$(echo "$INPUT" | jq -r '.topic // empty')
28
+ PLATFORM=$(echo "$INPUT" | jq -r '.platform // empty')
29
+ LINE=$(echo "$INPUT" | jq -r '.line // "crewly"')
30
+ TONE=$(echo "$INPUT" | jq -r '.tone // "professional"')
31
+ LENGTH=$(echo "$INPUT" | jq -r '.length // "medium"')
32
+ AUDIENCE=$(echo "$INPUT" | jq -r '.audience // empty')
33
+ CONTEXT=$(echo "$INPUT" | jq -r '.context // empty')
34
+ REFERENCES=$(echo "$INPUT" | jq -r '.references // empty')
35
+ CTA=$(echo "$INPUT" | jq -r '.cta // empty')
36
+
37
+ require_param "topic" "$TOPIC"
38
+ require_param "platform" "$PLATFORM"
39
+
40
+ # Platform-specific writing specs
41
+ PLATFORM_SPEC=""
42
+ CHAR_LIMIT=""
43
+ FORMAT_GUIDE=""
44
+ HASHTAG_GUIDE=""
45
+
46
+ case "$PLATFORM" in
47
+ x-thread)
48
+ CHAR_LIMIT="280 chars per tweet, 3-7 tweets total"
49
+ FORMAT_GUIDE="Start with a powerful hook tweet that stops scrolling. Each tweet should have one clear idea. Use [1/N] format. Line breaks between logical sections within tweets. Last tweet = CTA + hashtags."
50
+ HASHTAG_GUIDE="2-3 hashtags on the final tweet only. Suggested: #AIAgent #BuildInPublic #Automation"
51
+ ;;
52
+ x-single)
53
+ CHAR_LIMIT="280 chars total"
54
+ FORMAT_GUIDE="One punchy insight or announcement. No fluff. Include a hook and a takeaway in the same breath."
55
+ HASHTAG_GUIDE="1-2 hashtags max, integrated into the text if possible."
56
+ ;;
57
+ linkedin)
58
+ CHAR_LIMIT="1500-2500 chars"
59
+ FORMAT_GUIDE="Bold opening line (acts as preview). Short paragraphs (1-2 sentences). Use line breaks generously. Include data or specific examples. End with a question or CTA to drive comments. Structure: Hook > Problem > Insight > Evidence > Takeaway > CTA."
60
+ HASHTAG_GUIDE="3-5 hashtags at the very end, separated from body. Suggested: #AIAgents #SMBAutomation #BuildInPublic #AITeam"
61
+ ;;
62
+ xiaohongshu)
63
+ CHAR_LIMIT="500-1000 chars (Chinese)"
64
+ FORMAT_GUIDE="Write in Simplified Chinese. Catchy title with emoji in first line. Relatable and conversational. Use bullet points. Include personal anecdote or experience. End with engagement prompt (ask a question or invite comments)."
65
+ HASHTAG_GUIDE="5-8 hashtags in #topic[话题]# format at the end. Core tags: #创业MVP[话题]# #用好ai拿捏职场[话题]# #职场smalltalk[话题]# #vibecoding[话题]#"
66
+ ;;
67
+ substack)
68
+ CHAR_LIMIT="2000-8000 chars"
69
+ FORMAT_GUIDE="Newsletter format. Compelling subject line + subtitle. Structured with H2/H3 sections. Personal voice — write as Steve talking to subscribers. Include one 'aha moment' insight. Reference specific tools/data. End with: what to try this week + teaser for next issue."
70
+ HASHTAG_GUIDE="No hashtags. Use SEO-friendly title instead."
71
+ ;;
72
+ youtube-desc)
73
+ CHAR_LIMIT="2000-4000 chars"
74
+ FORMAT_GUIDE="First 2 lines = video summary (shown before 'Show more'). Include timestamps: [00:00] Intro, [01:30] Topic A, etc. Links section: relevant tools, socials, subscribe CTA. Bottom: 15-20 keyword tags comma-separated."
75
+ HASHTAG_GUIDE="No hashtags in description. Use keyword tags at the bottom."
76
+ ;;
77
+ blog)
78
+ CHAR_LIMIT="3000-10000 chars"
79
+ FORMAT_GUIDE="SEO-optimized blog post. H1 title with primary keyword. H2/H3 section structure. Include code examples where relevant. Add meta description (155 chars) at the top. Internal + external links. Conclusion with CTA."
80
+ HASHTAG_GUIDE="No hashtags. Focus on SEO keywords in headers and first paragraph."
81
+ ;;
82
+ *)
83
+ error_exit "Unsupported platform: $PLATFORM. Valid: x-thread, x-single, linkedin, xiaohongshu, substack, youtube-desc, blog"
84
+ ;;
85
+ esac
86
+
87
+ # Brand voice
88
+ BRAND_VOICE=""
89
+ case "$LINE" in
90
+ crewly)
91
+ BRAND_VOICE="Crewly brand voice. Technical but accessible. Developer-first audience. Honest about limitations. Concrete examples > abstract claims. Core positioning: 'Your AI Team, Ready in Days — Not Months.' Key differentiators: PTY isolation, Quality Gates, live terminal streaming, multi-agent orchestration."
92
+ ;;
93
+ personal)
94
+ BRAND_VOICE="Steve Huang's personal voice. Google PM (7 years). 24 side projects veteran. AI-native builder. Build in Public advocate. Bilingual EN/CN. Persona: smart friend sharing real experience, not guru lecturing. Key themes: one-person company, AI as leverage, side hustle strategy, shipping fast."
95
+ ;;
96
+ esac
97
+
98
+ # Tone
99
+ TONE_SPEC=""
100
+ case "$TONE" in
101
+ professional) TONE_SPEC="Authoritative and measured. Data-driven. Confident but not arrogant." ;;
102
+ casual) TONE_SPEC="Conversational. Like texting a smart friend. Humor welcome. Short sentences." ;;
103
+ technical) TONE_SPEC="Precise. Assumes reader is a developer. Use specific terms, code refs, architecture details." ;;
104
+ inspiring) TONE_SPEC="Forward-looking. Personal stories as evidence. Empowering. 'You can do this too' energy." ;;
105
+ provocative) TONE_SPEC="Contrarian takes. Challenge assumptions. Bold opinions backed by reasoning. 'Here is what everyone gets wrong' framing." ;;
106
+ educational) TONE_SPEC="Step-by-step. Clear explanations. Assume reader is learning. Use analogies." ;;
107
+ *) TONE_SPEC="Professional and clear." ;;
108
+ esac
109
+
110
+ # Length
111
+ LENGTH_SPEC=""
112
+ case "$LENGTH" in
113
+ short) LENGTH_SPEC="Keep it tight. Minimum viable content. Every word earns its place." ;;
114
+ medium) LENGTH_SPEC="Standard length for the platform. Develop 2-3 key points." ;;
115
+ long) LENGTH_SPEC="In-depth treatment. Multiple sections. Comprehensive but not padded." ;;
116
+ *) LENGTH_SPEC="Standard length for the platform." ;;
117
+ esac
118
+
119
+ # Audience
120
+ AUDIENCE_SPEC=""
121
+ if [ -n "$AUDIENCE" ]; then
122
+ AUDIENCE_SPEC="Target audience: ${AUDIENCE}."
123
+ else
124
+ case "$LINE" in
125
+ crewly) AUDIENCE_SPEC="Target audience: SMB founders, content agency owners, tech leads looking for AI automation. Decision makers who evaluate tools." ;;
126
+ personal) AUDIENCE_SPEC="Target audience: Tech professionals, aspiring entrepreneurs, side-hustle builders, AI enthusiasts. People who want to build things." ;;
127
+ esac
128
+ fi
129
+
130
+ # Build the writing brief
131
+ DRAFT_ID="draft-$(date +%s)-$((RANDOM % 1000))"
132
+ NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
133
+
134
+ BRIEF=$(jq -n \
135
+ --arg draftId "$DRAFT_ID" \
136
+ --arg topic "$TOPIC" \
137
+ --arg platform "$PLATFORM" \
138
+ --arg line "$LINE" \
139
+ --arg charLimit "$CHAR_LIMIT" \
140
+ --arg formatGuide "$FORMAT_GUIDE" \
141
+ --arg hashtagGuide "$HASHTAG_GUIDE" \
142
+ --arg brandVoice "$BRAND_VOICE" \
143
+ --arg toneSpec "$TONE_SPEC" \
144
+ --arg lengthSpec "$LENGTH_SPEC" \
145
+ --arg audienceSpec "$AUDIENCE_SPEC" \
146
+ --arg context "$CONTEXT" \
147
+ --arg references "$REFERENCES" \
148
+ --arg cta "$CTA" \
149
+ --arg createdAt "$NOW" \
150
+ '{
151
+ draftId: $draftId,
152
+ topic: $topic,
153
+ platform: $platform,
154
+ contentLine: $line,
155
+ createdAt: $createdAt,
156
+ writingBrief: {
157
+ charLimit: $charLimit,
158
+ formatGuide: $formatGuide,
159
+ hashtagGuide: $hashtagGuide,
160
+ brandVoice: $brandVoice,
161
+ tone: $toneSpec,
162
+ length: $lengthSpec,
163
+ audience: $audienceSpec,
164
+ additionalContext: $context,
165
+ references: $references,
166
+ callToAction: $cta
167
+ },
168
+ instruction: "Use this writing brief to generate the actual content. Write the full draft based on these specs, then use the save action to store it."
169
+ }')
170
+
171
+ echo "$BRIEF"
172
+ ;;
173
+
174
+ # ─────────────────────────────────────────────
175
+ # SAVE: Save a completed content draft to disk
176
+ # ─────────────────────────────────────────────
177
+ save)
178
+ DRAFT_ID=$(echo "$INPUT" | jq -r '.draftId // empty')
179
+ TITLE=$(echo "$INPUT" | jq -r '.title // empty')
180
+ PLATFORM=$(echo "$INPUT" | jq -r '.platform // empty')
181
+ LINE=$(echo "$INPUT" | jq -r '.line // "crewly"')
182
+ CONTENT=$(echo "$INPUT" | jq -r '.content // empty')
183
+ CALENDAR_ID=$(echo "$INPUT" | jq -r '.calendarId // empty')
184
+
185
+ require_param "title" "$TITLE"
186
+ require_param "platform" "$PLATFORM"
187
+ require_param "content" "$CONTENT"
188
+
189
+ # Generate draft ID if not provided
190
+ if [ -z "$DRAFT_ID" ]; then
191
+ DRAFT_ID="draft-$(date +%s)-$((RANDOM % 1000))"
192
+ fi
193
+
194
+ NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
195
+ TODAY=$(date -u +%Y-%m-%d)
196
+
197
+ # Create platform subdirectory
198
+ PLATFORM_DIR="${CONTENT_DIR}/${PLATFORM}"
199
+ mkdir -p "$PLATFORM_DIR"
200
+
201
+ # Generate filename
202
+ SAFE_TITLE=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-' | head -c 50)
203
+ FILENAME="${TODAY}-${SAFE_TITLE}.md"
204
+ FILE_PATH="${PLATFORM_DIR}/${FILENAME}"
205
+
206
+ # Write the draft as markdown
207
+ cat > "$FILE_PATH" <<DRAFT_EOF
208
+ ---
209
+ draftId: ${DRAFT_ID}
210
+ title: ${TITLE}
211
+ platform: ${PLATFORM}
212
+ line: ${LINE}
213
+ calendarId: ${CALENDAR_ID}
214
+ createdAt: ${NOW}
215
+ status: draft
216
+ ---
217
+
218
+ ${CONTENT}
219
+ DRAFT_EOF
220
+
221
+ CHAR_COUNT=${#CONTENT}
222
+
223
+ jq -n \
224
+ --arg draftId "$DRAFT_ID" \
225
+ --arg title "$TITLE" \
226
+ --arg platform "$PLATFORM" \
227
+ --arg filePath "$FILE_PATH" \
228
+ --argjson charCount "$CHAR_COUNT" \
229
+ --arg calendarId "$CALENDAR_ID" \
230
+ '{"success":true,"action":"save","draftId":$draftId,"title":$title,"platform":$platform,"filePath":$filePath,"charCount":$charCount,"calendarId":$calendarId}'
231
+ ;;
232
+
233
+ # ─────────────────────────────────────────────
234
+ # GET: Read a saved draft
235
+ # ─────────────────────────────────────────────
236
+ get)
237
+ FILE_PATH=$(echo "$INPUT" | jq -r '.filePath // empty')
238
+ DRAFT_ID=$(echo "$INPUT" | jq -r '.draftId // empty')
239
+
240
+ if [ -n "$FILE_PATH" ] && [ -f "$FILE_PATH" ]; then
241
+ CONTENT=$(cat "$FILE_PATH")
242
+ jq -n --arg content "$CONTENT" --arg filePath "$FILE_PATH" \
243
+ '{"success":true,"action":"get","filePath":$filePath,"content":$content}'
244
+ elif [ -n "$DRAFT_ID" ]; then
245
+ # Search by draft ID in all files
246
+ FOUND=""
247
+ for f in "$CONTENT_DIR"/*/*.md "$CONTENT_DIR"/*.md; do
248
+ [ -f "$f" ] || continue
249
+ if grep -q "draftId: ${DRAFT_ID}" "$f" 2>/dev/null; then
250
+ FOUND="$f"
251
+ break
252
+ fi
253
+ done
254
+ if [ -n "$FOUND" ]; then
255
+ CONTENT=$(cat "$FOUND")
256
+ jq -n --arg content "$CONTENT" --arg filePath "$FOUND" \
257
+ '{"success":true,"action":"get","filePath":$filePath,"content":$content}'
258
+ else
259
+ error_exit "Draft not found: $DRAFT_ID"
260
+ fi
261
+ else
262
+ error_exit "Provide filePath or draftId"
263
+ fi
264
+ ;;
265
+
266
+ # ─────────────────────────────────────────────
267
+ # LIST: List saved drafts
268
+ # ─────────────────────────────────────────────
269
+ list)
270
+ FILTER_PLATFORM=$(echo "$INPUT" | jq -r '.platform // empty')
271
+ LIMIT=$(echo "$INPUT" | jq -r '.limit // "20"')
272
+
273
+ DRAFTS="[]"
274
+
275
+ SEARCH_DIRS="$CONTENT_DIR"
276
+ if [ -n "$FILTER_PLATFORM" ]; then
277
+ SEARCH_DIRS="${CONTENT_DIR}/${FILTER_PLATFORM}"
278
+ fi
279
+
280
+ for f in $(find "$SEARCH_DIRS" -name "*.md" -type f 2>/dev/null | sort -r | head -"$LIMIT"); do
281
+ [ -f "$f" ] || continue
282
+ BASENAME=$(basename "$f")
283
+ DIR_PLATFORM=$(basename "$(dirname "$f")")
284
+ # Extract frontmatter fields
285
+ D_TITLE=$(grep "^title:" "$f" 2>/dev/null | head -1 | sed 's/^title: //')
286
+ D_PLATFORM=$(grep "^platform:" "$f" 2>/dev/null | head -1 | sed 's/^platform: //')
287
+ D_LINE=$(grep "^line:" "$f" 2>/dev/null | head -1 | sed 's/^line: //')
288
+ D_STATUS=$(grep "^status:" "$f" 2>/dev/null | head -1 | sed 's/^status: //')
289
+ D_CREATED=$(grep "^createdAt:" "$f" 2>/dev/null | head -1 | sed 's/^createdAt: //')
290
+ FILE_SIZE=$(wc -c < "$f" | tr -d ' ')
291
+
292
+ DRAFTS=$(echo "$DRAFTS" | jq \
293
+ --arg file "$f" \
294
+ --arg title "${D_TITLE:-$BASENAME}" \
295
+ --arg platform "${D_PLATFORM:-$DIR_PLATFORM}" \
296
+ --arg line "${D_LINE:-unknown}" \
297
+ --arg status "${D_STATUS:-draft}" \
298
+ --arg created "${D_CREATED:-unknown}" \
299
+ --arg size "$FILE_SIZE" \
300
+ '. + [{"filePath":$file,"title":$title,"platform":$platform,"line":$line,"status":$status,"createdAt":$created,"fileSize":($size|tonumber)}]')
301
+ done
302
+
303
+ COUNT=$(echo "$DRAFTS" | jq 'length')
304
+ jq -n --argjson drafts "$DRAFTS" --argjson count "$COUNT" \
305
+ '{"success":true,"action":"list","count":$count,"drafts":$drafts}'
306
+ ;;
307
+
308
+ *)
309
+ error_exit "Unknown action: $ACTION. Valid: draft, save, get, list"
310
+ ;;
311
+ esac