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.
- package/README.md +6 -6
- package/config/roles/ops/prompt.md +140 -0
- package/config/roles/ops/role.json +13 -0
- package/config/skills/agent/browse-stealth/execute.sh +84 -0
- package/config/skills/agent/browse-stealth/instructions.md +108 -0
- package/config/skills/agent/browse-stealth/launch-chrome-cdp.sh +141 -0
- package/config/skills/agent/browse-stealth/skill.json +20 -0
- package/config/skills/agent/browse-stealth/stealth-browse.py +330 -0
- package/config/skills/agent/competitor-content-tracker/execute.sh +232 -0
- package/config/skills/agent/competitor-content-tracker/instructions.md +210 -0
- package/config/skills/agent/competitor-content-tracker/skill.json +22 -0
- package/config/skills/agent/content-calendar/execute.sh +294 -0
- package/config/skills/agent/content-calendar/instructions.md +122 -0
- package/config/skills/agent/content-calendar/skill.json +22 -0
- package/config/skills/agent/content-repurposer/execute.sh +194 -0
- package/config/skills/agent/content-repurposer/instructions.md +69 -0
- package/config/skills/agent/content-repurposer/skill.json +22 -0
- package/config/skills/agent/content-writer/execute.sh +311 -0
- package/config/skills/agent/content-writer/instructions.md +124 -0
- package/config/skills/agent/content-writer/skill.json +22 -0
- package/config/skills/agent/core/generate-pdf/execute.sh +88 -0
- package/config/skills/agent/core/generate-pdf/instructions.md +46 -0
- package/config/skills/agent/core/generate-pdf/skill.json +20 -0
- package/config/skills/agent/core/report-status/execute.sh +6 -0
- package/config/skills/agent/trend-monitor/execute.sh +211 -0
- package/config/skills/agent/trend-monitor/instructions.md +207 -0
- package/config/skills/agent/trend-monitor/skill.json +22 -0
- package/config/skills/agent/vnc-browser/execute.sh +261 -0
- package/config/skills/agent/vnc-browser/instructions.md +102 -0
- package/config/skills/agent/vnc-browser/skill.json +20 -0
- package/config/skills/orchestrator/delegate-task/execute.sh +63 -4
- package/config/skills/orchestrator/delegate-task/instructions.md +60 -0
- package/config/skills/orchestrator/delegate-task/skill.json +4 -4
- package/config/skills/orchestrator/reply-slack/execute.sh +2 -0
- package/config/skills/orchestrator/send-key/execute.sh +19 -6
- package/config/skills/orchestrator/send-key/instructions.md +44 -0
- package/config/skills/orchestrator/send-key/skill.json +20 -0
- package/config/skills/orchestrator/send-message/execute.sh +9 -1
- package/config/skills/registry.json +256 -0
- package/config/templates/code-review-team/README.md +176 -0
- package/config/templates/code-review-team/team-config.json +16 -0
- package/config/templates/code-review-team.json +62 -0
- package/config/templates/content-generation-team/README.md +128 -0
- package/config/templates/content-generation-team/team-config.json +21 -0
- package/config/templates/content-generation-team.json +67 -0
- package/config/templates/demo-team.json +22 -0
- package/config/templates/social-media-ops-team/README.md +145 -0
- package/config/templates/social-media-ops-team/team-config.json +21 -0
- package/config/templates/social-media-ops-team.json +67 -0
- package/dist/backend/backend/src/constants.d.ts +69 -6
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +75 -6
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/index.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/index.js +2 -0
- package/dist/backend/backend/src/controllers/index.js.map +1 -1
- package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts +8 -0
- package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/messaging/messenger.routes.js +110 -63
- package/dist/backend/backend/src/controllers/messaging/messenger.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +31 -4
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/oauth/oauth.routes.d.ts +8 -0
- package/dist/backend/backend/src/controllers/oauth/oauth.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/oauth/oauth.routes.js +127 -111
- package/dist/backend/backend/src/controllers/oauth/oauth.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts +34 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js +219 -2
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/user/user.routes.d.ts +7 -0
- package/dist/backend/backend/src/controllers/user/user.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/user/user.routes.js +45 -38
- package/dist/backend/backend/src/controllers/user/user.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/whatsapp/index.d.ts +17 -0
- package/dist/backend/backend/src/controllers/whatsapp/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/whatsapp/index.js +18 -0
- package/dist/backend/backend/src/controllers/whatsapp/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts +12 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js +185 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts +5 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +35 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/modules/task-management.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/modules/task-management.routes.js +4 -0
- package/dist/backend/backend/src/routes/modules/task-management.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-heartbeat.service.js +1 -1
- package/dist/backend/backend/src/services/agent/agent-heartbeat.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +14 -3
- 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 +160 -29
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts +4 -3
- package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/claude-runtime.service.js +29 -4
- package/dist/backend/backend/src/services/agent/claude-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/context-window-monitor.service.js +11 -0
- package/dist/backend/backend/src/services/agent/context-window-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +32 -2
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +69 -8
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +14 -2
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js +11 -2
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.d.ts +18 -0
- package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.js +28 -4
- package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.js +2 -2
- package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.d.ts +18 -0
- package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.js +26 -4
- package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts +28 -2
- package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/messenger-registry.service.d.ts +33 -2
- package/dist/backend/backend/src/services/messaging/messenger-registry.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/messenger-registry.service.js +33 -0
- package/dist/backend/backend/src/services/messaging/messenger-registry.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js +4 -2
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js +4 -3
- package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js.map +1 -1
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts +27 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project/task-tracking.service.js +54 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +36 -6
- 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 +238 -36
- 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 +6 -4
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/user/user-identity.service.d.ts +44 -0
- package/dist/backend/backend/src/services/user/user-identity.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/user/user-identity.service.js +75 -8
- package/dist/backend/backend/src/services/user/user-identity.service.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/index.d.ts +11 -0
- package/dist/backend/backend/src/services/whatsapp/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/index.js +11 -0
- package/dist/backend/backend/src/services/whatsapp/index.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts +66 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js +96 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts +109 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +234 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts +127 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js +347 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js.map +1 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/scheduler.service.js +4 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -1
- package/dist/backend/backend/src/types/index.d.ts +1 -0
- package/dist/backend/backend/src/types/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/index.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +24 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/types/task-tracking.types.d.ts +4 -0
- package/dist/backend/backend/src/types/task-tracking.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/task-tracking.types.js.map +1 -1
- package/dist/backend/backend/src/types/whatsapp.types.d.ts +84 -0
- package/dist/backend/backend/src/types/whatsapp.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/whatsapp.types.js +33 -0
- package/dist/backend/backend/src/types/whatsapp.types.js.map +1 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts +11 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.js +35 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +69 -6
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +75 -6
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js +14 -2
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
- package/dist/cli/backend/src/types/index.d.ts +1 -0
- package/dist/cli/backend/src/types/index.d.ts.map +1 -1
- package/dist/cli/backend/src/types/index.js.map +1 -1
- package/dist/cli/cli/src/commands/publish.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/publish.js +17 -15
- package/dist/cli/cli/src/commands/publish.js.map +1 -1
- package/dist/cli/cli/src/index.js +2 -2
- package/dist/cli/cli/src/index.js.map +1 -1
- package/dist/cli/cli/src/utils/gh-submit.d.ts +46 -0
- package/dist/cli/cli/src/utils/gh-submit.d.ts.map +1 -0
- package/dist/cli/cli/src/utils/gh-submit.js +167 -0
- package/dist/cli/cli/src/utils/gh-submit.js.map +1 -0
- package/dist/cli/cli/src/utils/marketplace.d.ts.map +1 -1
- package/dist/cli/cli/src/utils/marketplace.js +13 -5
- package/dist/cli/cli/src/utils/marketplace.js.map +1 -1
- package/dist/cli/cli/src/utils/templates.d.ts +3 -2
- package/dist/cli/cli/src/utils/templates.d.ts.map +1 -1
- package/dist/cli/cli/src/utils/templates.js +5 -4
- package/dist/cli/cli/src/utils/templates.js.map +1 -1
- package/frontend/dist/assets/{index-45eeea99.js → index-a23214ae.js} +241 -241
- package/frontend/dist/assets/{index-6972eeee.css → index-c407fe13.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +3 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Content Writer
|
|
2
|
+
|
|
3
|
+
Generate platform-specific content drafts with brand voice, tone, and format guidelines. Manages the full lifecycle from writing brief to saved draft.
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
1. Generate writing brief → execute.sh '{"action":"draft",...}'
|
|
9
|
+
2. Write content using brief → Agent uses LLM to write
|
|
10
|
+
3. Save completed draft → execute.sh '{"action":"save",...}'
|
|
11
|
+
4. Add to content calendar → content-calendar skill
|
|
12
|
+
5. Steve reviews + publishes → Manual
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Actions
|
|
16
|
+
|
|
17
|
+
### `draft` — Generate a writing brief
|
|
18
|
+
|
|
19
|
+
Returns structured guidelines for writing a specific piece of content. The agent then uses these to generate the actual text.
|
|
20
|
+
|
|
21
|
+
| Parameter | Required | Description |
|
|
22
|
+
|-----------|----------|-------------|
|
|
23
|
+
| `action` | Yes | `"draft"` |
|
|
24
|
+
| `topic` | Yes | What to write about |
|
|
25
|
+
| `platform` | Yes | `x-thread`, `x-single`, `linkedin`, `xiaohongshu`, `substack`, `youtube-desc`, `blog` |
|
|
26
|
+
| `line` | No | `crewly` (brand) or `personal` (Steve). Default: `crewly` |
|
|
27
|
+
| `tone` | No | `professional`, `casual`, `technical`, `inspiring`, `provocative`, `educational`. Default: `professional` |
|
|
28
|
+
| `length` | No | `short`, `medium`, `long`. Default: `medium` |
|
|
29
|
+
| `audience` | No | Custom audience description (overrides default) |
|
|
30
|
+
| `context` | No | Additional context: reference data, trend info, competitor analysis |
|
|
31
|
+
| `references` | No | URLs or docs to reference |
|
|
32
|
+
| `cta` | No | Specific call-to-action to include |
|
|
33
|
+
|
|
34
|
+
### `save` — Save a completed draft
|
|
35
|
+
|
|
36
|
+
| Parameter | Required | Description |
|
|
37
|
+
|-----------|----------|-------------|
|
|
38
|
+
| `action` | Yes | `"save"` |
|
|
39
|
+
| `title` | Yes | Content title |
|
|
40
|
+
| `platform` | Yes | Platform name |
|
|
41
|
+
| `content` | Yes | The full content text (markdown) |
|
|
42
|
+
| `line` | No | Content line (default: crewly) |
|
|
43
|
+
| `draftId` | No | Draft ID from the brief (for traceability) |
|
|
44
|
+
| `calendarId` | No | Content calendar entry ID to link |
|
|
45
|
+
|
|
46
|
+
### `get` — Read a saved draft
|
|
47
|
+
|
|
48
|
+
| Parameter | Required | Description |
|
|
49
|
+
|-----------|----------|-------------|
|
|
50
|
+
| `action` | Yes | `"get"` |
|
|
51
|
+
| `filePath` | Yes* | Path to the draft file |
|
|
52
|
+
| `draftId` | Yes* | Or search by draft ID |
|
|
53
|
+
|
|
54
|
+
*One of filePath or draftId required.
|
|
55
|
+
|
|
56
|
+
### `list` — List saved drafts
|
|
57
|
+
|
|
58
|
+
| Parameter | Required | Description |
|
|
59
|
+
|-----------|----------|-------------|
|
|
60
|
+
| `action` | Yes | `"list"` |
|
|
61
|
+
| `platform` | No | Filter by platform |
|
|
62
|
+
| `limit` | No | Max results (default: 20) |
|
|
63
|
+
|
|
64
|
+
## Examples
|
|
65
|
+
|
|
66
|
+
### Generate a writing brief for an X thread
|
|
67
|
+
```bash
|
|
68
|
+
bash execute.sh '{"action":"draft","topic":"AI agent security - what we learned from n8n 8 CVEs","platform":"x-thread","line":"crewly","tone":"provocative","context":"n8n disclosed 8 critical CVEs on 2/25/2026 including RCE via sandbox escape. Crewly uses PTY isolation."}'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Generate a brief for a Xiaohongshu post
|
|
72
|
+
```bash
|
|
73
|
+
bash execute.sh '{"action":"draft","topic":"My AI team shipped while I slept - week 1 report","platform":"xiaohongshu","line":"personal","tone":"casual"}'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Save a completed draft
|
|
77
|
+
```bash
|
|
78
|
+
bash execute.sh '{"action":"save","title":"AI agent security thread","platform":"x-thread","line":"crewly","content":"[1/5] n8n just disclosed 8 critical CVEs...\n\n[2/5] The scariest one?...","projectPath":"/path/to/project"}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### List all LinkedIn drafts
|
|
82
|
+
```bash
|
|
83
|
+
bash execute.sh '{"action":"list","platform":"linkedin","projectPath":"/path/to/project"}'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Platform Writing Tips
|
|
87
|
+
|
|
88
|
+
### X Thread Best Practices
|
|
89
|
+
- Hook tweet is everything — if they don't stop scrolling, nothing else matters
|
|
90
|
+
- Each tweet = one idea, one screenshot, or one data point
|
|
91
|
+
- Use numbers: "3 things I learned" > "Things I learned"
|
|
92
|
+
- End with a thread-pull: "If this was useful, follow me for more"
|
|
93
|
+
- Steve's top performers on Xiaohongshu all had strong visual hooks — apply same principle
|
|
94
|
+
|
|
95
|
+
### LinkedIn Best Practices
|
|
96
|
+
- First line shows in feed preview — make it impossible to not click "see more"
|
|
97
|
+
- "I" stories outperform "You should" advice
|
|
98
|
+
- Specific > general: "We saved 47 hours/week" > "We saved a lot of time"
|
|
99
|
+
- Post between 8-10am EST Tuesday-Thursday for max reach
|
|
100
|
+
|
|
101
|
+
### Xiaohongshu Best Practices (from Steve's data)
|
|
102
|
+
- Video outperforms image-text (20 videos vs 12 image-text in Steve's archive)
|
|
103
|
+
- Top tags to use: 创业MVP, 职场smalltalk, 用好ai拿捏职场, vibecoding
|
|
104
|
+
- Steve's top post (201 likes): "Claude Code牛马工厂" — visual + relatable + specific tool
|
|
105
|
+
- Social/community posts (Google coffee chat: 164 likes) also perform well
|
|
106
|
+
- Include 8+ hashtags for discovery
|
|
107
|
+
|
|
108
|
+
### Substack Best Practices
|
|
109
|
+
- Subject line = open rate. Test: numbers, questions, or contrarian takes
|
|
110
|
+
- Personal stories in the opening paragraph build connection
|
|
111
|
+
- Include 1 actionable takeaway readers can use this week
|
|
112
|
+
- Cross-promote: mention X thread, YouTube video in each issue
|
|
113
|
+
|
|
114
|
+
## Brand Voice Quick Reference
|
|
115
|
+
|
|
116
|
+
### Crewly Brand
|
|
117
|
+
- **Do:** Use specific metrics, show product screenshots, reference real use cases
|
|
118
|
+
- **Don't:** Claim "revolutionary" or "game-changing", oversell features not yet built
|
|
119
|
+
- **Signature phrases:** "AI Team", "Ready in Days", "Quality Gates", "Live Terminal"
|
|
120
|
+
|
|
121
|
+
### Steve Personal
|
|
122
|
+
- **Do:** Share real numbers, mention Google/side projects, be vulnerable about failures
|
|
123
|
+
- **Don't:** Lecture, be preachy, use generic "hustle" motivation
|
|
124
|
+
- **Signature phrases:** "一人公司", "下班2小时", "留在牌桌上", "Build in Public"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "content-writer",
|
|
3
|
+
"name": "Content Writer",
|
|
4
|
+
"description": "Generate platform-specific writing briefs and manage content drafts. Supports X threads, LinkedIn posts, Xiaohongshu notes, Substack newsletters, YouTube descriptions, and blog posts. Includes brand voice presets for Crewly and Steve's personal content.",
|
|
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"],
|
|
17
|
+
"triggers": ["write content", "create post", "draft content", "write thread", "write article", "content draft"],
|
|
18
|
+
"tags": ["content", "writing", "draft", "social-media", "marketing", "x-thread", "linkedin", "xiaohongshu", "substack", "youtube", "blog"],
|
|
19
|
+
"version": "1.0.0",
|
|
20
|
+
"author": "Luna (Content Strategist)",
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Generate PDF from Markdown with CJK font support
|
|
3
|
+
# Uses pandoc (md→html) + weasyprint (html→pdf)
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
7
|
+
|
|
8
|
+
INPUT="${1:-}"
|
|
9
|
+
[ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"input\":\"/tmp/report.md\",\"output\":\"/tmp/report.pdf\",\"title\":\"My Report\"}'"
|
|
10
|
+
|
|
11
|
+
MD_PATH=$(echo "$INPUT" | jq -r '.input // empty')
|
|
12
|
+
PDF_PATH=$(echo "$INPUT" | jq -r '.output // empty')
|
|
13
|
+
TITLE=$(echo "$INPUT" | jq -r '.title // empty')
|
|
14
|
+
|
|
15
|
+
require_param "input" "$MD_PATH"
|
|
16
|
+
|
|
17
|
+
# Default output: same path with .pdf extension
|
|
18
|
+
if [ -z "$PDF_PATH" ]; then
|
|
19
|
+
PDF_PATH="${MD_PATH%.md}.pdf"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Intermediate HTML path
|
|
23
|
+
HTML_PATH="${PDF_PATH%.pdf}.html"
|
|
24
|
+
|
|
25
|
+
# Default title from filename
|
|
26
|
+
if [ -z "$TITLE" ]; then
|
|
27
|
+
TITLE=$(basename "$MD_PATH" .md | tr '-' ' ' | tr '_' ' ')
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Ensure the CJK CSS header exists
|
|
31
|
+
CJK_HEADER="/tmp/cjk-header.html"
|
|
32
|
+
if [ ! -f "$CJK_HEADER" ]; then
|
|
33
|
+
cat > "$CJK_HEADER" << 'CSSEOF'
|
|
34
|
+
<style>
|
|
35
|
+
body {
|
|
36
|
+
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
|
|
37
|
+
max-width: 900px;
|
|
38
|
+
margin: 0 auto;
|
|
39
|
+
padding: 2rem;
|
|
40
|
+
line-height: 1.8;
|
|
41
|
+
color: #1a1a1a;
|
|
42
|
+
font-size: 14px;
|
|
43
|
+
}
|
|
44
|
+
h1, h2, h3, h4 {
|
|
45
|
+
font-family: -apple-system, "PingFang SC", "Hiragino Sans GB", sans-serif;
|
|
46
|
+
margin-top: 1.5em;
|
|
47
|
+
color: #111;
|
|
48
|
+
}
|
|
49
|
+
h1 { font-size: 1.8em; border-bottom: 2px solid #333; padding-bottom: 0.3em; }
|
|
50
|
+
h2 { font-size: 1.4em; border-bottom: 1px solid #ccc; padding-bottom: 0.2em; }
|
|
51
|
+
h3 { font-size: 1.15em; }
|
|
52
|
+
table { border-collapse: collapse; width: 100%; margin: 1em 0; font-size: 13px; }
|
|
53
|
+
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
|
54
|
+
th { background: #f5f5f5; font-weight: 600; }
|
|
55
|
+
tr:nth-child(even) { background: #fafafa; }
|
|
56
|
+
blockquote { border-left: 4px solid #4a90d9; margin: 1em 0; padding: 0.5em 1em; background: #f0f6ff; color: #333; }
|
|
57
|
+
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-size: 0.9em; }
|
|
58
|
+
hr { border: none; border-top: 1px solid #ddd; margin: 2em 0; }
|
|
59
|
+
strong { color: #111; }
|
|
60
|
+
</style>
|
|
61
|
+
CSSEOF
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Check dependencies
|
|
65
|
+
if ! command -v pandoc &>/dev/null; then
|
|
66
|
+
error_exit "pandoc is not installed. Run: brew install pandoc"
|
|
67
|
+
fi
|
|
68
|
+
if ! command -v weasyprint &>/dev/null; then
|
|
69
|
+
error_exit "weasyprint is not installed. Run: brew install weasyprint"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Step 1: Markdown → HTML (with CJK CSS header)
|
|
73
|
+
pandoc "$MD_PATH" -o "$HTML_PATH" \
|
|
74
|
+
--standalone \
|
|
75
|
+
--metadata title="$TITLE" \
|
|
76
|
+
--include-in-header="$CJK_HEADER" 2>/dev/null
|
|
77
|
+
|
|
78
|
+
# Step 2: HTML → PDF (weasyprint handles CJK fonts correctly)
|
|
79
|
+
weasyprint "$HTML_PATH" "$PDF_PATH" 2>/dev/null
|
|
80
|
+
|
|
81
|
+
# Get file size
|
|
82
|
+
PDF_SIZE=$(wc -c < "$PDF_PATH" | tr -d ' ')
|
|
83
|
+
|
|
84
|
+
# Clean up intermediate HTML
|
|
85
|
+
rm -f "$HTML_PATH"
|
|
86
|
+
|
|
87
|
+
# Output result
|
|
88
|
+
echo "{\"success\":true,\"pdf\":\"${PDF_PATH}\",\"size\":${PDF_SIZE}}"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Generate PDF
|
|
2
|
+
|
|
3
|
+
Convert a Markdown file to a professionally styled PDF with full CJK (Chinese/Japanese/Korean) font support.
|
|
4
|
+
|
|
5
|
+
## Pipeline
|
|
6
|
+
|
|
7
|
+
1. **Markdown → HTML**: Uses `pandoc` with `--standalone` and an inline CJK-compatible CSS header
|
|
8
|
+
2. **HTML → PDF**: Uses `weasyprint` (NOT Chrome headless, which cannot render CJK characters)
|
|
9
|
+
|
|
10
|
+
## Parameters
|
|
11
|
+
|
|
12
|
+
| Parameter | Required | Description |
|
|
13
|
+
|-----------|----------|-------------|
|
|
14
|
+
| `input` | Yes | Path to the input Markdown file |
|
|
15
|
+
| `output` | No | Path for the output PDF file (defaults to input path with `.pdf` extension) |
|
|
16
|
+
| `title` | No | Document title for the HTML metadata |
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bash config/skills/agent/core/generate-pdf/execute.sh '{"input":"/tmp/report.md","output":"/tmp/report.pdf","title":"My Report"}'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Minimal usage (auto-generates output path)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bash config/skills/agent/core/generate-pdf/execute.sh '{"input":"/tmp/report.md"}'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This produces `/tmp/report.pdf`.
|
|
31
|
+
|
|
32
|
+
## Important Notes
|
|
33
|
+
|
|
34
|
+
- **Always use this skill** instead of manually running pandoc + Chrome headless
|
|
35
|
+
- **Chrome headless cannot render Chinese/CJK characters** — they appear blank in the PDF
|
|
36
|
+
- **weasyprint** properly embeds CJK fonts (PingFang SC, Hiragino Sans GB, STHeiti) on macOS
|
|
37
|
+
- The skill auto-generates a CSS header with CJK font declarations if one doesn't exist
|
|
38
|
+
- Output is a clean, styled PDF suitable for sharing via Slack or email
|
|
39
|
+
|
|
40
|
+
## Output
|
|
41
|
+
|
|
42
|
+
JSON with the path to the generated PDF file:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{"success": true, "pdf": "/tmp/report.pdf", "size": 364024}
|
|
46
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "agent-generate-pdf",
|
|
3
|
+
"name": "Generate PDF",
|
|
4
|
+
"description": "Convert a Markdown file to a styled PDF with full CJK (Chinese/Japanese/Korean) font support. Uses pandoc for Markdown→HTML and weasyprint for HTML→PDF rendering.",
|
|
5
|
+
"category": "document",
|
|
6
|
+
"skillType": "claude-skill",
|
|
7
|
+
"promptFile": "instructions.md",
|
|
8
|
+
"execution": {
|
|
9
|
+
"type": "script",
|
|
10
|
+
"script": {
|
|
11
|
+
"file": "execute.sh",
|
|
12
|
+
"interpreter": "bash",
|
|
13
|
+
"timeoutMs": 60000
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"assignableRoles": ["developer", "qa", "tpm", "designer", "frontend-developer", "backend-developer", "fullstack-dev", "qa-engineer", "product-manager", "architect", "generalist", "sales", "support", "content-strategist"],
|
|
17
|
+
"triggers": ["generate pdf", "convert to pdf", "create pdf", "markdown to pdf", "make pdf"],
|
|
18
|
+
"tags": ["pdf", "document", "markdown", "pandoc", "weasyprint", "cjk"],
|
|
19
|
+
"version": "1.0.0"
|
|
20
|
+
}
|
|
@@ -33,3 +33,9 @@ if [ "$STATUS" = "done" ] && [ -n "$TASK_PATH" ]; then
|
|
|
33
33
|
'{taskPath: $taskPath, sessionName: $sessionName}')
|
|
34
34
|
api_call POST "/task-management/complete" "$COMPLETE_BODY" || true
|
|
35
35
|
fi
|
|
36
|
+
|
|
37
|
+
# Auto-complete tracked tasks when status is done
|
|
38
|
+
if [ "$STATUS" = "done" ]; then
|
|
39
|
+
SESSION_BODY=$(jq -n --arg sessionName "$SESSION_NAME" '{sessionName: $sessionName}')
|
|
40
|
+
api_call POST "/task-management/complete-by-session" "$SESSION_BODY" || true
|
|
41
|
+
fi
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Trend Monitor — save, read, and manage trend data from browser scans
|
|
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\":\"save|list|latest|suggest\",\"projectPath\":\"/path/to/project\",...}'"
|
|
9
|
+
|
|
10
|
+
ACTION=$(echo "$INPUT" | jq -r '.action // empty')
|
|
11
|
+
PROJECT_PATH=$(echo "$INPUT" | jq -r '.projectPath // empty')
|
|
12
|
+
|
|
13
|
+
require_param "action" "$ACTION"
|
|
14
|
+
|
|
15
|
+
# Resolve trends directory
|
|
16
|
+
if [ -n "$PROJECT_PATH" ]; then
|
|
17
|
+
TRENDS_DIR="${PROJECT_PATH}/.crewly/content/trends"
|
|
18
|
+
else
|
|
19
|
+
TRENDS_DIR="${HOME}/.crewly/content/trends"
|
|
20
|
+
fi
|
|
21
|
+
mkdir -p "$TRENDS_DIR"
|
|
22
|
+
|
|
23
|
+
case "$ACTION" in
|
|
24
|
+
|
|
25
|
+
# ─────────────────────────────────────────────
|
|
26
|
+
# SAVE: Store a batch of trends from a browser scan
|
|
27
|
+
# ─────────────────────────────────────────────
|
|
28
|
+
save)
|
|
29
|
+
SOURCE=$(echo "$INPUT" | jq -r '.source // empty')
|
|
30
|
+
TRENDS=$(echo "$INPUT" | jq -r '.trends // empty')
|
|
31
|
+
|
|
32
|
+
require_param "source" "$SOURCE"
|
|
33
|
+
require_param "trends" "$TRENDS"
|
|
34
|
+
|
|
35
|
+
# Validate source
|
|
36
|
+
case "$SOURCE" in
|
|
37
|
+
x-trending|google-trends|hackernews|producthunt|reddit|github-trending|custom) ;;
|
|
38
|
+
*) error_exit "Invalid source: $SOURCE. Valid: x-trending, google-trends, hackernews, producthunt, reddit, github-trending, custom" ;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
# Validate trends is a JSON array
|
|
42
|
+
if ! echo "$TRENDS" | jq 'type == "array"' 2>/dev/null | grep -q true; then
|
|
43
|
+
error_exit "trends must be a JSON array of objects"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
47
|
+
TODAY=$(date -u +%Y-%m-%d)
|
|
48
|
+
SCAN_ID="scan-$(date +%s)-$((RANDOM % 1000))"
|
|
49
|
+
|
|
50
|
+
# Add metadata to each trend
|
|
51
|
+
ENRICHED=$(echo "$TRENDS" | jq --arg source "$SOURCE" --arg scanId "$SCAN_ID" --arg ts "$NOW" \
|
|
52
|
+
'[.[] | . + {"source": $source, "scanId": $scanId, "scannedAt": $ts}]')
|
|
53
|
+
|
|
54
|
+
# Count items
|
|
55
|
+
COUNT=$(echo "$ENRICHED" | jq 'length')
|
|
56
|
+
|
|
57
|
+
# Save to date-based file
|
|
58
|
+
SCAN_FILE="${TRENDS_DIR}/${TODAY}-${SOURCE}.json"
|
|
59
|
+
|
|
60
|
+
if [ -f "$SCAN_FILE" ]; then
|
|
61
|
+
# Append to existing file
|
|
62
|
+
EXISTING=$(cat "$SCAN_FILE")
|
|
63
|
+
MERGED=$(jq -n --argjson existing "$EXISTING" --argjson new "$ENRICHED" \
|
|
64
|
+
'{"scans": ($existing.scans + [{"scanId": $new[0].scanId, "scannedAt": $new[0].scannedAt, "count": ($new | length), "items": $new}])}')
|
|
65
|
+
echo "$MERGED" > "$SCAN_FILE"
|
|
66
|
+
else
|
|
67
|
+
# Create new file
|
|
68
|
+
jq -n --argjson items "$ENRICHED" --arg source "$SOURCE" --arg date "$TODAY" \
|
|
69
|
+
'{"source": $source, "date": $date, "scans": [{"scanId": $items[0].scanId, "scannedAt": $items[0].scannedAt, "count": ($items | length), "items": $items}]}' > "$SCAN_FILE"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
jq -n \
|
|
73
|
+
--arg scanId "$SCAN_ID" \
|
|
74
|
+
--arg source "$SOURCE" \
|
|
75
|
+
--argjson count "$COUNT" \
|
|
76
|
+
--arg file "$SCAN_FILE" \
|
|
77
|
+
--arg scannedAt "$NOW" \
|
|
78
|
+
'{"success":true,"action":"save","scanId":$scanId,"source":$source,"count":$count,"file":$file,"scannedAt":$scannedAt}'
|
|
79
|
+
;;
|
|
80
|
+
|
|
81
|
+
# ─────────────────────────────────────────────
|
|
82
|
+
# LIST: List available trend scans
|
|
83
|
+
# ─────────────────────────────────────────────
|
|
84
|
+
list)
|
|
85
|
+
FILTER_SOURCE=$(echo "$INPUT" | jq -r '.source // empty')
|
|
86
|
+
FILTER_DATE=$(echo "$INPUT" | jq -r '.date // empty')
|
|
87
|
+
LIMIT=$(echo "$INPUT" | jq -r '.limit // "10"')
|
|
88
|
+
|
|
89
|
+
FILES="[]"
|
|
90
|
+
for f in "$TRENDS_DIR"/*.json; do
|
|
91
|
+
[ -f "$f" ] || continue
|
|
92
|
+
BASENAME=$(basename "$f" .json)
|
|
93
|
+
FILE_DATE=$(echo "$BASENAME" | cut -d'-' -f1-3)
|
|
94
|
+
FILE_SOURCE=$(echo "$BASENAME" | cut -d'-' -f4-)
|
|
95
|
+
|
|
96
|
+
# Apply filters
|
|
97
|
+
if [ -n "$FILTER_SOURCE" ] && [ "$FILE_SOURCE" != "$FILTER_SOURCE" ]; then
|
|
98
|
+
continue
|
|
99
|
+
fi
|
|
100
|
+
if [ -n "$FILTER_DATE" ] && [ "$FILE_DATE" != "$FILTER_DATE" ]; then
|
|
101
|
+
continue
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
SCAN_COUNT=$(jq '.scans | length' "$f" 2>/dev/null || echo "0")
|
|
105
|
+
TOTAL_ITEMS=$(jq '[.scans[].count] | add // 0' "$f" 2>/dev/null || echo "0")
|
|
106
|
+
LAST_SCAN=$(jq -r '.scans[-1].scannedAt // "unknown"' "$f" 2>/dev/null || echo "unknown")
|
|
107
|
+
|
|
108
|
+
FILES=$(echo "$FILES" | jq \
|
|
109
|
+
--arg file "$f" \
|
|
110
|
+
--arg date "$FILE_DATE" \
|
|
111
|
+
--arg source "$FILE_SOURCE" \
|
|
112
|
+
--argjson scanCount "$SCAN_COUNT" \
|
|
113
|
+
--argjson totalItems "$TOTAL_ITEMS" \
|
|
114
|
+
--arg lastScan "$LAST_SCAN" \
|
|
115
|
+
'. + [{"file":$file,"date":$date,"source":$source,"scanCount":$scanCount,"totalItems":$totalItems,"lastScan":$lastScan}]')
|
|
116
|
+
done
|
|
117
|
+
|
|
118
|
+
# Sort by date descending and limit
|
|
119
|
+
FILES=$(echo "$FILES" | jq --argjson limit "$LIMIT" 'sort_by(.date) | reverse | .[:$limit]')
|
|
120
|
+
COUNT=$(echo "$FILES" | jq 'length')
|
|
121
|
+
|
|
122
|
+
jq -n --argjson files "$FILES" --argjson count "$COUNT" \
|
|
123
|
+
'{"success":true,"action":"list","count":$count,"files":$files}'
|
|
124
|
+
;;
|
|
125
|
+
|
|
126
|
+
# ─────────────────────────────────────────────
|
|
127
|
+
# LATEST: Get the latest trends from a specific source
|
|
128
|
+
# ─────────────────────────────────────────────
|
|
129
|
+
latest)
|
|
130
|
+
FILTER_SOURCE=$(echo "$INPUT" | jq -r '.source // empty')
|
|
131
|
+
LIMIT=$(echo "$INPUT" | jq -r '.limit // "20"')
|
|
132
|
+
|
|
133
|
+
# Find most recent file(s)
|
|
134
|
+
ALL_ITEMS="[]"
|
|
135
|
+
|
|
136
|
+
for f in $(ls -t "$TRENDS_DIR"/*.json 2>/dev/null | head -5); do
|
|
137
|
+
[ -f "$f" ] || continue
|
|
138
|
+
FILE_SOURCE=$(basename "$f" .json | cut -d'-' -f4-)
|
|
139
|
+
|
|
140
|
+
if [ -n "$FILTER_SOURCE" ] && [ "$FILE_SOURCE" != "$FILTER_SOURCE" ]; then
|
|
141
|
+
continue
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Get items from the latest scan in each file
|
|
145
|
+
ITEMS=$(jq '.scans[-1].items // []' "$f" 2>/dev/null || echo "[]")
|
|
146
|
+
ALL_ITEMS=$(jq -n --argjson a "$ALL_ITEMS" --argjson b "$ITEMS" '$a + $b')
|
|
147
|
+
done
|
|
148
|
+
|
|
149
|
+
# Sort by relevance score if available, limit
|
|
150
|
+
ALL_ITEMS=$(echo "$ALL_ITEMS" | jq --argjson limit "$LIMIT" \
|
|
151
|
+
'sort_by(.relevanceScore // 0) | reverse | .[:$limit]')
|
|
152
|
+
COUNT=$(echo "$ALL_ITEMS" | jq 'length')
|
|
153
|
+
|
|
154
|
+
jq -n --argjson items "$ALL_ITEMS" --argjson count "$COUNT" \
|
|
155
|
+
'{"success":true,"action":"latest","count":$count,"items":$items}'
|
|
156
|
+
;;
|
|
157
|
+
|
|
158
|
+
# ─────────────────────────────────────────────
|
|
159
|
+
# SUGGEST: Generate topic suggestions from recent trends
|
|
160
|
+
# ─────────────────────────────────────────────
|
|
161
|
+
suggest)
|
|
162
|
+
CONTENT_LINE=$(echo "$INPUT" | jq -r '.line // "crewly"')
|
|
163
|
+
LIMIT=$(echo "$INPUT" | jq -r '.limit // "5"')
|
|
164
|
+
|
|
165
|
+
# Gather all recent trend items (last 3 days)
|
|
166
|
+
ALL_ITEMS="[]"
|
|
167
|
+
for f in $(ls -t "$TRENDS_DIR"/*.json 2>/dev/null | head -10); do
|
|
168
|
+
[ -f "$f" ] || continue
|
|
169
|
+
ITEMS=$(jq '.scans[-1].items // []' "$f" 2>/dev/null || echo "[]")
|
|
170
|
+
ALL_ITEMS=$(jq -n --argjson a "$ALL_ITEMS" --argjson b "$ITEMS" '$a + $b')
|
|
171
|
+
done
|
|
172
|
+
|
|
173
|
+
COUNT=$(echo "$ALL_ITEMS" | jq 'length')
|
|
174
|
+
|
|
175
|
+
if [ "$COUNT" -eq 0 ]; then
|
|
176
|
+
jq -n '{"success":true,"action":"suggest","suggestions":[],"message":"No trend data available. Run a browser scan first using the instructions in instructions.md."}'
|
|
177
|
+
exit 0
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Filter for AI-related trends
|
|
181
|
+
AI_ITEMS=$(echo "$ALL_ITEMS" | jq '[.[] | select(
|
|
182
|
+
(.title // "" | test("(?i)ai|agent|llm|gpt|claude|gemini|automation|saas|startup")) or
|
|
183
|
+
(.relevanceScore // 0) >= 7
|
|
184
|
+
)]')
|
|
185
|
+
|
|
186
|
+
AI_COUNT=$(echo "$AI_ITEMS" | jq 'length')
|
|
187
|
+
|
|
188
|
+
# Build suggestion context
|
|
189
|
+
TOPICS=$(echo "$AI_ITEMS" | jq --argjson limit "$LIMIT" \
|
|
190
|
+
'[.[:$limit] | .[] | {title: .title, source: .source, url: (.url // ""), relevanceScore: (.relevanceScore // 0)}]')
|
|
191
|
+
|
|
192
|
+
jq -n \
|
|
193
|
+
--argjson allTrends "$COUNT" \
|
|
194
|
+
--argjson aiRelevant "$AI_COUNT" \
|
|
195
|
+
--argjson topics "$TOPICS" \
|
|
196
|
+
--arg line "$CONTENT_LINE" \
|
|
197
|
+
'{
|
|
198
|
+
"success": true,
|
|
199
|
+
"action": "suggest",
|
|
200
|
+
"totalTrendsScanned": $allTrends,
|
|
201
|
+
"aiRelevantTrends": $aiRelevant,
|
|
202
|
+
"contentLine": $line,
|
|
203
|
+
"suggestedTopics": $topics,
|
|
204
|
+
"instruction": "Use these trending topics as input for content-writer skill. Match with your content calendar and brand voice."
|
|
205
|
+
}'
|
|
206
|
+
;;
|
|
207
|
+
|
|
208
|
+
*)
|
|
209
|
+
error_exit "Unknown action: $ACTION. Valid: save, list, latest, suggest"
|
|
210
|
+
;;
|
|
211
|
+
esac
|