@jclvsh/dropspace 2.11.0 → 2.13.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 CHANGED
@@ -42,21 +42,21 @@ dropspace launches --help
42
42
 
43
43
  ### CLI Commands
44
44
 
45
- | Group | Commands | Count |
46
- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- |
47
- | launches | list, create, get, update, delete, publish, retry, generate, retry-content, analytics, batch-analytics, status, delete-post, delete-posts | 14 |
48
- | personas | list, create, get, update, delete, analyze | 6 |
49
- | media | generate, job | 2 |
50
- | connections | list | 1 |
51
- | boosts | create, list, get, activate, cancel, performance | 6 |
52
- | reddit | suggest, config, configure, discover, comments, approve, reject, standing, activate, deactivate | 10 |
53
- | credits | balance, purchase | 2 |
54
- | keys | me, list, create, rename, revoke, rotate | 6 |
55
- | webhooks | list, create, get, update, delete, test, rotate-secret, deliveries | 8 |
56
- | status | dropspace official account connections | 1 |
57
- | usage | plan, limits, and billing period | 1 |
58
- | autopilot | status, configure, review, approve, reject, run, runs | 7 |
59
- | recommendations (recs) | list, apply, dismiss | 3 |
45
+ | Group | Commands | Count |
46
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- |
47
+ | launches | list, create, get, update, delete, publish, retry, generate, retry-content, analytics, batch-analytics, status, delete-post, delete-posts | 14 |
48
+ | personas | list, create, get, update, delete, analyze | 6 |
49
+ | media | generate, job | 2 |
50
+ | connections | list | 1 |
51
+ | boosts | create, list, get, activate, cancel, performance | 6 |
52
+ | reddit | suggest, config, configure, discover, comments, approve, reject, standing, activate, deactivate | 10 |
53
+ | credits | balance, purchase | 2 |
54
+ | keys | me, list, create, rename, revoke, rotate | 6 |
55
+ | webhooks | list, create, get, update, delete, test, rotate-secret, deliveries | 8 |
56
+ | status | dropspace official account connections | 1 |
57
+ | usage | plan, limits, and billing period | 1 |
58
+ | autopilot | status, configure, review, approve, reject, run, runs | 7 |
59
+ | recommendations (recs) | list, apply, dismiss | 3 |
60
60
 
61
61
  ## MCP Server Configuration
62
62
 
@@ -109,41 +109,41 @@ Add to your `.cursor/mcp.json`:
109
109
 
110
110
  ### Launches
111
111
 
112
- | Tool | Description |
113
- | ---------------------- | -------------------------------------------------------------- |
114
- | `list_launches` | List your launches with pagination |
115
- | `create_launch` | Create a new launch with content, media, and account selection |
116
- | `get_launch` | Get a launch by ID with posting status |
117
- | `update_launch` | Update a launch's schedule, status, content, or media |
118
- | `delete_launch` | Delete a launch |
119
- | `publish_launch` | Publish a launch to all configured platforms |
120
- | `retry_launch` | Retry failed platforms for a launch |
121
- | `generate_content` | Generate AI content and video scripts for a launch |
122
- | `retry_launch_content` | Retry AI content generation for failed platforms |
123
- | `get_launch_analytics` | Get publishing analytics with per-post engagement metrics |
124
- | `get_batch_launch_analytics` | Get analytics for multiple launches in one call |
125
- | `get_launch_status` | Get detailed posting status with per-platform logs |
112
+ | Tool | Description |
113
+ | ---------------------------- | -------------------------------------------------------------- |
114
+ | `list_launches` | List your launches with pagination |
115
+ | `create_launch` | Create a new launch with content, media, and account selection |
116
+ | `get_launch` | Get a launch by ID with posting status |
117
+ | `update_launch` | Update a launch's schedule, status, content, or media |
118
+ | `delete_launch` | Delete a launch |
119
+ | `publish_launch` | Publish a launch to all configured platforms |
120
+ | `retry_launch` | Retry failed platforms for a launch |
121
+ | `generate_content` | Generate AI content and video scripts for a launch |
122
+ | `retry_launch_content` | Retry AI content generation for failed platforms |
123
+ | `get_launch_analytics` | Get publishing analytics with per-post engagement metrics |
124
+ | `get_batch_launch_analytics` | Get analytics for multiple launches in one call |
125
+ | `get_launch_status` | Get detailed posting status with per-platform logs |
126
126
 
127
127
  #### `create_launch` Parameters
128
128
 
129
- | Parameter | Type | Required | Description |
130
- | ----------------------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
131
- | `title` | string | Yes | Launch title |
132
- | `product_description` | string | Yes | Product description |
133
- | `platforms` | string[] | Yes | Platforms to publish to (1-9) |
134
- | `product_url` | string | No | Product URL |
135
- | `scheduled_date` | string | No | ISO 8601 datetime (15+ min in the future) |
136
- | `persona_id` | string | No | Persona ID for tone/style |
137
- | `dropspace_platforms` | string[] | No | Platforms to post via official dropspace accounts |
138
- | `media_mode` | string | No | `"images"` or `"video"` |
139
- | `user_platform_accounts` | object | No | Map of platform key to token_id (UUID). Simple keys: `twitter`, `reddit`, `instagram`, `tiktok`. LinkedIn: `linkedin:personal` or `linkedin:organization:<org_id>`. Facebook: `facebook:page:<page_id>` |
129
+ | Parameter | Type | Required | Description |
130
+ | ----------------------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
131
+ | `title` | string | Yes | Launch title |
132
+ | `product_description` | string | Yes | Product description |
133
+ | `platforms` | string[] | Yes | Platforms to publish to (1-9) |
134
+ | `product_url` | string | No | Product URL |
135
+ | `scheduled_date` | string | No | ISO 8601 datetime (15+ min in the future) |
136
+ | `persona_id` | string | No | Persona ID for tone/style |
137
+ | `dropspace_platforms` | string[] | No | Platforms to post via official dropspace accounts |
138
+ | `media_mode` | string | No | `"images"` or `"video"` |
139
+ | `user_platform_accounts` | object | No | Map of platform key to token_id (UUID). Simple keys: `twitter`, `reddit`, `instagram`, `tiktok`. LinkedIn: `linkedin:personal` or `linkedin:organization:<org_id>`. Facebook: `facebook:page:<page_id>` |
140
140
  | `platform_contents` | object | No | Per-platform content (mutually exclusive with `custom_content`). Reddit accepts `metadata.subreddits` (string[], max 10, no `r/` prefix — one post per subreddit, each retryable) and `metadata.first_comment` (max 1500) |
141
- | `custom_content` | string \| string[] | No | Single content for all platforms, or array of tweets for twitter thread mode (mutually exclusive with `platform_contents`) |
142
- | `custom_content_reddit_title` | string | No | Reddit title (required with `custom_content` + reddit) |
143
- | `media` | object[] | No | Inline media, URL or base64 (1-10 items, mutually exclusive with `media_assets`) |
144
- | `media_assets` | object[] | No | Pre-uploaded media references (mutually exclusive with `media`) |
145
- | `media_attach_platforms` | string[] | No | Platforms to attach media to |
146
- | `generate_ai_videos` | string[] | No | Auto-generate AI videos (`"instagram"`, `"tiktok"`) |
141
+ | `custom_content` | string \| string[] | No | Single content for all platforms, or array of tweets for twitter thread mode (mutually exclusive with `platform_contents`) |
142
+ | `custom_content_reddit_title` | string | No | Reddit title (required with `custom_content` + reddit) |
143
+ | `media` | object[] | No | Inline media, URL or base64 (1-10 items, mutually exclusive with `media_assets`) |
144
+ | `media_assets` | object[] | No | Pre-uploaded media references (mutually exclusive with `media`) |
145
+ | `media_attach_platforms` | string[] | No | Platforms to attach media to |
146
+ | `generate_ai_videos` | string[] | No | Auto-generate AI videos (`"instagram"`, `"tiktok"`) |
147
147
 
148
148
  #### `update_launch` Parameters
149
149
 
@@ -170,16 +170,16 @@ Add to your `.cursor/mcp.json`:
170
170
 
171
171
  ### Personas
172
172
 
173
- | Tool | Description |
174
- | ----------------- | ------------------------------------------------ |
175
- | `list_personas` | List your personas |
176
- | `create_persona` | Create a new persona for content tone/style |
177
- | `get_persona` | Get a persona by ID with analysis details |
178
- | `update_persona` | Update a persona's name or writing samples |
179
- | `delete_persona` | Delete a persona |
180
- | `analyze_persona` | Trigger AI analysis of a persona's writing style |
181
- | `finalize_persona` | Accept a completed analysis so the persona is usable |
182
- | `discard_persona_analysis` | Discard a pending analysis result |
173
+ | Tool | Description |
174
+ | -------------------------- | ---------------------------------------------------- |
175
+ | `list_personas` | List your personas |
176
+ | `create_persona` | Create a new persona for content tone/style |
177
+ | `get_persona` | Get a persona by ID with analysis details |
178
+ | `update_persona` | Update a persona's name or writing samples |
179
+ | `delete_persona` | Delete a persona |
180
+ | `analyze_persona` | Trigger AI analysis of a persona's writing style |
181
+ | `finalize_persona` | Accept a completed analysis so the persona is usable |
182
+ | `discard_persona_analysis` | Discard a pending analysis result |
183
183
 
184
184
  #### `analyze_persona` Parameters
185
185
 
@@ -205,10 +205,10 @@ Add to your `.cursor/mcp.json`:
205
205
 
206
206
  ### Media
207
207
 
208
- | Tool | Description |
209
- | ---------------- | ---------------------------------------------------------------------------- |
210
- | `generate_media` | Submit an AI image or video generation job for a launch (returns a job ID) |
211
- | `get_media_job` | Poll a media generation job's status; returns the result URL when complete |
208
+ | Tool | Description |
209
+ | ---------------- | -------------------------------------------------------------------------- |
210
+ | `generate_media` | Submit an AI image or video generation job for a launch (returns a job ID) |
211
+ | `get_media_job` | Poll a media generation job's status; returns the result URL when complete |
212
212
 
213
213
  ### Connections
214
214
 
@@ -218,14 +218,14 @@ Add to your `.cursor/mcp.json`:
218
218
 
219
219
  ### Boosts
220
220
 
221
- | Tool | Description |
222
- | ----------------------- | ------------------------------------------------------------------------ |
221
+ | Tool | Description |
222
+ | ----------------------- | ------------------------------------------------------------------------- |
223
223
  | `create_boost` | Create a paid Meta boost on an organic Facebook/Instagram launch (paused) |
224
- | `list_boosts` | List all boosts attached to a launch |
225
- | `get_boost` | Get a boost by ID with current performance metrics |
226
- | `activate_boost` | Activate a paused boost — ad spend begins immediately |
227
- | `cancel_boost` | Cancel a boost (deletes the Meta campaign, irreversible) |
228
- | `get_boost_performance` | Fetch latest boost metrics (impressions, clicks, reach, spend) |
224
+ | `list_boosts` | List all boosts attached to a launch |
225
+ | `get_boost` | Get a boost by ID with current performance metrics |
226
+ | `activate_boost` | Activate a paused boost — ad spend begins immediately |
227
+ | `cancel_boost` | Cancel a boost (deletes the Meta campaign, irreversible) |
228
+ | `get_boost_performance` | Fetch latest boost metrics (impressions, clicks, reach, spend) |
229
229
 
230
230
  ### Credits
231
231
 
@@ -316,11 +316,11 @@ Autopilot is available on pro and premium plans.
316
316
 
317
317
  ### Recommendations
318
318
 
319
- | Tool | Description |
320
- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
321
- | `list_recommendations` | List actionable insights from your organic post performance (winners, boost/promote/pause signals) |
319
+ | Tool | Description |
320
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------- |
321
+ | `list_recommendations` | List actionable insights from your organic post performance (winners, boost/promote/pause signals) |
322
322
  | `apply_recommendation` | Apply the one-click action: draft launch, PAUSED boost (needs `ad_account_id`), autopilot queue, directive |
323
- | `dismiss_recommendation` | Dismiss a pending recommendation |
323
+ | `dismiss_recommendation` | Dismiss a pending recommendation |
324
324
 
325
325
  Applying never publishes or spends by itself — drafts need scheduling and boosts need explicit activation.
326
326
 
@@ -26,7 +26,7 @@ export function registerKeyCommands(program, client) {
26
26
  keys.command("create")
27
27
  .description("create a new API key (requires admin scope)")
28
28
  .requiredOption("-n, --name <name>", "key name")
29
- .option("--scopes <list>", "comma-separated scopes: read,write,generate,send,admin (legacy publish/delete still accepted)", parseList)
29
+ .option("--scopes <list>", "comma-separated scopes: read,write,generate,send,admin (defaults to read,write when omitted)", parseList)
30
30
  .action(async (opts) => {
31
31
  try {
32
32
  const body = { name: opts.name };
@@ -5,7 +5,9 @@ import { formatOutput, handleError, getFormat } from "../cli-utils.js";
5
5
  * account has reddit connected; caps and gates are enforced server-side (hard rails).
6
6
  */
7
7
  export function registerRedditCommands(program, client) {
8
- const reddit = program.command("reddit").description("subreddit discovery + engagement (earn standing before promoting)");
8
+ const reddit = program
9
+ .command("reddit")
10
+ .description("subreddit discovery + engagement (earn standing before promoting)");
9
11
  reddit
10
12
  .command("suggest")
11
13
  .description("ICP-matched subreddit discovery with per-community standing badges")
@@ -4,7 +4,9 @@
4
4
  * Phase 1: facebook only, engagement objective, auto-targeting. API key auth.
5
5
  */
6
6
  import { z } from "zod";
7
- const platformSchema = z.enum(["facebook", "instagram"]).describe("ad platform — facebook or instagram (IG boost requires the IG business account linked to one of the user's facebook pages)");
7
+ const platformSchema = z
8
+ .enum(["facebook", "instagram"])
9
+ .describe("ad platform — facebook or instagram (IG boost requires the IG business account linked to one of the user's facebook pages)");
8
10
  const objectiveSchema = z.literal("engagement").describe("campaign objective (phase 1: engagement only)");
9
11
  const launchIdParam = z.string().uuid().describe("launch ID to boost — must have a successful facebook post");
10
12
  const boostIdParam = z.string().uuid().describe("boost ID");
@@ -37,7 +39,12 @@ export function registerBoostTools(server, client) {
37
39
  }
38
40
  catch (error) {
39
41
  return {
40
- content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
42
+ content: [
43
+ {
44
+ type: "text",
45
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
46
+ },
47
+ ],
41
48
  isError: true,
42
49
  };
43
50
  }
@@ -49,7 +56,12 @@ export function registerBoostTools(server, client) {
49
56
  }
50
57
  catch (error) {
51
58
  return {
52
- content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
59
+ content: [
60
+ {
61
+ type: "text",
62
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
63
+ },
64
+ ],
53
65
  isError: true,
54
66
  };
55
67
  }
@@ -61,7 +73,12 @@ export function registerBoostTools(server, client) {
61
73
  }
62
74
  catch (error) {
63
75
  return {
64
- content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
80
+ },
81
+ ],
65
82
  isError: true,
66
83
  };
67
84
  }
@@ -73,7 +90,12 @@ export function registerBoostTools(server, client) {
73
90
  }
74
91
  catch (error) {
75
92
  return {
76
- content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
93
+ content: [
94
+ {
95
+ type: "text",
96
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
97
+ },
98
+ ],
77
99
  isError: true,
78
100
  };
79
101
  }
@@ -81,11 +103,18 @@ export function registerBoostTools(server, client) {
81
103
  server.tool("cancel_boost", "cancel a boost — deletes the campaign on meta and soft-deletes the dropspace record. final, irreversible.", { launch_id: launchIdParam, boost_id: boostIdParam }, async ({ launch_id, boost_id }) => {
82
104
  try {
83
105
  await client.delete(`/launches/${launch_id}/boost/${boost_id}`);
84
- return { content: [{ type: "text", text: JSON.stringify({ success: true, boost_id }, null, 2) }] };
106
+ return {
107
+ content: [{ type: "text", text: JSON.stringify({ success: true, boost_id }, null, 2) }],
108
+ };
85
109
  }
86
110
  catch (error) {
87
111
  return {
88
- content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
116
+ },
117
+ ],
89
118
  isError: true,
90
119
  };
91
120
  }
@@ -97,7 +126,12 @@ export function registerBoostTools(server, client) {
97
126
  }
98
127
  catch (error) {
99
128
  return {
100
- content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
133
+ },
134
+ ],
101
135
  isError: true,
102
136
  };
103
137
  }
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  // Cross-repo canonical scope vocabulary (2026-05-07): read/write/generate/send/admin.
3
- // Pre-rename: `delete` was folded into `write` (soft-deletes are writes); `publish` is now `send`.
4
- // Existing keys with legacy scope values continue to authenticate via SCOPE_ALIASES on the server.
3
+ // Legacy values (publish/delete) were fully retired 2026-07-20 - all rows backfilled canonical.
5
4
  const SCOPES = ["read", "write", "generate", "send", "admin"];
6
5
  export function registerKeyTools(server, client) {
7
6
  server.tool("get_current_api_key", "get the current API key's info — name, scopes, and prefix (no scope required)", {}, async () => {
@@ -44,7 +43,7 @@ export function registerKeyTools(server, client) {
44
43
  });
45
44
  server.tool("create_api_key", "create a new API key — the raw key is returned once, store it securely (requires admin scope)", {
46
45
  name: z.string().min(1).max(100).describe("key name (1-100 characters)"),
47
- scopes: z.array(z.enum(SCOPES)).optional().describe("permission scopes (defaults to all scopes)"),
46
+ scopes: z.array(z.enum(SCOPES)).optional().describe("permission scopes (defaults to read,write when omitted)"),
48
47
  }, async (args) => {
49
48
  try {
50
49
  const result = await client.post("/keys", args);
@@ -84,7 +84,11 @@ export function registerLaunchTools(server, client) {
84
84
  .max(10)
85
85
  .optional()
86
86
  .describe("target platforms - required unless dropspace_platforms or user_platform_accounts is provided (inferred from their union)"),
87
- product_url: z.string().url().optional().describe("product URL — scraped to generate content; can substitute for product_description"),
87
+ product_url: z
88
+ .string()
89
+ .url()
90
+ .optional()
91
+ .describe("product URL — scraped to generate content; can substitute for product_description"),
88
92
  scheduled_date: z
89
93
  .string()
90
94
  .optional()
@@ -18,4 +18,21 @@ export function registerUsageTools(server, client) {
18
18
  };
19
19
  }
20
20
  });
21
+ server.tool("get_changelog", "machine-readable release history (public) - version, date, type, summary, migration notes on breaking entries. the X-Notice header's structured backstory.", {}, async () => {
22
+ try {
23
+ const result = await client.get("/changelog");
24
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25
+ }
26
+ catch (error) {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
32
+ },
33
+ ],
34
+ isError: true,
35
+ };
36
+ }
37
+ });
21
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jclvsh/dropspace",
3
- "version": "2.11.0",
3
+ "version": "2.13.0",
4
4
  "description": "MCP server and CLI for the Dropspace API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,6 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "tsc",
18
+ "prepublishOnly": "npm run build",
18
19
  "start": "node dist/index.js"
19
20
  },
20
21
  "dependencies": {