@vruum/skills 0.6.34 → 0.6.35

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.34",
3
+ "version": "0.6.35",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.34",
3
+ "version": "0.6.35",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Skills for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vruum/skills",
3
- "version": "0.6.34",
3
+ "version": "0.6.35",
4
4
  "description": "Vruum AI skills for Claude Code, Claude Desktop, Codex CLI, and any AI assistant with a skill directory. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis. Pairs with the Vruum MCP server at https://api.vruum.ai/mcp.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,5 +42,5 @@
42
42
  "outreach",
43
43
  "gtm"
44
44
  ],
45
- "contentHash": "b6d9406f16c833820c0b442e41854781aaf9e57a12f817aea87e4f3075a35cca"
45
+ "contentHash": "e6f36061d77555954c948125566850ef21af1debb21e0b65371876caa29768d4"
46
46
  }
@@ -88,6 +88,17 @@ Then save it **once** with `manage_content` `action="draft"`, payload `{content,
88
88
 
89
89
  `action="draft"` creates a new draft row every time it runs. Save **once** per post, then show the seller the saved draft and iterate by editing.
90
90
 
91
+ ## Step 5.5: Attach media (optional — document/PDF carousel, image, or video)
92
+
93
+ Carousels (document posts) are the top-performing organic format. If the seller has (or you produce) a PDF, image, or video for this post:
94
+
95
+ 1. Store the asset: `manage_campaign` kind='ad' action='store_creative'. Small files: payload `{asset_base64, filename}`. Real files (PDFs/videos — primary path): `{filename, size_bytes, content_type: 'application/pdf' | 'video/mp4'}` → PUT the file to the returned `upload_url` (`curl --fail-with-body -T <file> '<upload_url>'`), then call store_creative again with `{creative_id}` to finalize.
96
+ 2. Attach it: include `attachment_creative_id` in the draft payload (or add it later with `action="edit"`). Explicit `attachment_creative_id: null` on edit detaches.
97
+ 3. The stored `filename` renders as the LinkedIn document **title** — name it like a headline, not `export-final-v3.pdf`.
98
+ 4. Before publishing, open the `attachment_url` from `get_content_review post_ids=[<post_id>]` and review the actual file — it publishes under the seller's identity.
99
+
100
+ The caption (`content`) is still required — an attachment never replaces the post text. Scheduled posts publish with their attachment automatically.
101
+
91
102
  ## Step 6: Iterate by editing — never re-save
92
103
 
93
104
  When the seller wants changes (tighten the hook, change the CTA, fix a line), revise the text yourself and update the **existing** draft with `manage_content` using `action="edit"`, passing the updated `content`.
@@ -46,9 +46,10 @@ Hold the settled audience (the resolved `{facets}` map — shown to the seller a
46
46
 
47
47
  **Visual — generate or supply it, then store it as a draft.**
48
48
 
49
- - **Image**: generate with your own image tools, then store via `manage_campaign` kind='ad' action='store_creative', payload `{image_base64 (raw base64, no data: URL prefix), generation_prompt, generation_provenance: {model, tool, generated_at, notes}}`.
50
- - **Video** (mp4, ≤200MB, 3s–30min): store via the same action — `{media_url: <public https url>}` for a hosted file, or `{filename, size_bytes, content_type:'video/mp4'}` to get a **presigned upload URL** for a local file (PUT it with curl, then call store_creative again with `{creative_id}` to finalize). Optional `{thumbnail_base64}`. **Video stores are async** poll `fetch type='ads' subtype='creative' id=<creative_id>` until `upload_status` leaves `'uploading'`; a `'failed'` status with a probe-code error means re-export the file, not retry.
51
- - A stored **video** creative can be attached to the organic post at publish (`manage_content action=publish` with `creative_id`)one asset serves the organic post AND the ad. Images remain library assets only (image attach stays out of scope for this loop).
49
+ - **Image**: generate with your own image tools, then store via `manage_campaign` kind='ad' action='store_creative', payload `{asset_base64 (raw base64, no data: URL prefix — renamed from image_base64 in VRU-726), generation_prompt, filename?, generation_provenance: {model, tool, generated_at, notes}}`.
50
+ - **Document / PDF carousel** (≤25MB — VRU-726, the top organic format): produce the PDF with your own tools, then store via the same action — small files: `{asset_base64, filename: 'my-deck.pdf'}` (no generation_prompt needed); real files (primary path): `{filename, size_bytes, content_type:'application/pdf'}` returns a **presigned upload_url**; `curl --fail-with-body -T my-deck.pdf '<upload_url>'`, then call store_creative again with `{creative_id}` to finalize. The `filename` becomes the rendered LinkedIn carousel **title**. Attach to a post with `manage_content` action='draft'|'edit' payload `{attachment_creative_id}`, open the `attachment_url` from `get_content_review post_ids=[...]` to review the actual file, then publish.
51
+ - **Video** (mp4, ≤200MB, 3s–30min): store via the same action `{media_url: <public https url>}` for a hosted file, or `{filename, size_bytes, content_type:'video/mp4'}` for the presigned flow above. Optional `{thumbnail_base64}`. **media_url stores are async** poll `fetch type='ads' subtype='creative' id=<creative_id>` until `upload_status` leaves `'uploading'`; a `'failed'` status with a probe-code error means re-export the file, not retry. Videos attach to posts the same way (`attachment_creative_id` at draft/edit the old publish-time `creative_id` param is retired), which also makes **scheduled video posts** carry their media.
52
+ - A stored creative (document/PDF, image, or video) attaches to the organic post at **draft/edit** (`manage_content` payload `{attachment_creative_id}`; explicit null detaches) — one asset serves the organic post AND the ad, and attachments persist on the row so scheduled posts publish with their media.
52
53
 
53
54
  A video creative can ALSO run without any post as **Direct Sponsored Content** (`boost` with `creative_id` instead of `content_post_id`) — but DSC is **metrics-only**: no organic post means no engager bridge. Prefer the published-post path when the bridge matters.
54
55
 
@@ -76,7 +77,7 @@ If the seller hesitates on any of the four, stop at draft and leave the loop res
76
77
  Only after the Step 4 approvals:
77
78
 
78
79
  0. **Identity prerequisites (first run only)** — a Page campaign needs the Company Page set (`manage_campaign` kind='ad' action='set_page'; call with no organization_urn to discover the candidates); a Thought Leader Ad needs the author authorized (action='authorize_author'). Errors from boost name the exact fixing call — run it and retry rather than improvising.
79
- 1. **Publish the organic post** — `manage_content action=publish` on the draft from `/create-content` (pass `creative_id` to attach an approved video). This inherits `/create-content`'s author guard: if the chosen author's LinkedIn account isn't connected/healthy, publish fails hard rather than posting under another identity — surface that to the seller, don't retry blindly. **Wait for the post to actually be `published`** (a video publish transfers media and can take a while — re-read the post before boosting; never boost a still-publishing post).
80
+ 1. **Publish the organic post** — first make sure the approved media is attached to the draft (`manage_content action=edit` payload `{attachment_creative_id}` — the old publish-time `creative_id` param is retired and now returns a 400), then `manage_content action=publish` on the draft from `/create-content`. This inherits `/create-content`'s author guard: if the chosen author's LinkedIn account isn't connected/healthy, publish fails hard rather than posting under another identity — surface that to the seller, don't retry blindly. **Wait for the post to actually be `published`** (a video publish transfers media and can take a while — re-read the post before boosting; never boost a still-publishing post).
80
81
  2. **Boost the published post** — `manage_campaign` kind='ad' action='boost', payload `{content_post_id: <the just-published post id>, vehicle?, objective: <the approved objective>, budget: {daily_budget_cents | total_budget_cents}, audience: {facets} OR {matched_audience_id}, duration_days?, approval_mode}`. Vehicle is inferred from the post's identity — pass the objective explicitly (the default is BRAND_AWARENESS, which is NOT what a click campaign wants). Use the `approval_mode` the seller authorized in Step 4 — `draft` unless they explicitly approved the budget for `auto`. The boost double-submit case is handled for you (idempotent per source + audience + vehicle + objective), so don't paper over a retry with a second call. (DSC alternative: `creative_id` instead of `content_post_id` runs the video without a post — metrics-only, no bridge.)
81
82
  3. **If the campaign involves a video ad**, the LinkedIn media upload runs in the background after approval — the response tells you; poll `fetch type='ads' subtype='campaign' id=<campaign_id>` (~every 30s) until it reports live or a failure with its cause.
82
83
  4. **Thought Leader boosts return a Campaign Manager handoff, not a live campaign** — LinkedIn's public API cannot attach a member's post to the campaign (verified live), so the boost/approve response comes back with `tla_manual_attach_required` plus a Campaign Manager deep link and the exact attach steps (Ads → Add ad → Sponsor existing content → Thought leader content → pick the post; the member approves sponsorship once → Launch). Relay the link + steps to the seller verbatim; this is the expected TLA flow, not an error. **While in Campaign Manager, have them verify bidding** — the default can be manual CPM (~$10/1k impressions), which barely delivers; switch to Maximum delivery. After they launch, run `manage_campaign` kind='ad' action='resume' id=<campaign_id> to sync the local status to live. Page-identity boosts are unaffected (fully API-driven end to end).