@jclvsh/dropspace 2.29.0 → 2.31.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/dist/commands/autopilot.js +3 -0
- package/dist/commands/boosts.js +4 -1
- package/dist/commands/media.js +61 -0
- package/dist/tool-policy.js +7 -0
- package/dist/tools/autopilot.js +4 -0
- package/dist/tools/boosts.d.ts +1 -1
- package/dist/tools/boosts.js +12 -2
- package/dist/tools/media.js +61 -0
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -31,6 +31,7 @@ export function registerAutopilotCommands(program, client) {
|
|
|
31
31
|
.option("--timezone <tz>", "IANA timezone, e.g. America/New_York")
|
|
32
32
|
.option("--product-url <url>", "product link included in posts")
|
|
33
33
|
.option("--product-image <url>", "slideshow reference image (\"none\" clears it)")
|
|
34
|
+
.option("--media-source <source>", "ai | library | mixed: where image and video posts get media")
|
|
34
35
|
.option("--brief <text>", "product brief (what you're building, who it's for)")
|
|
35
36
|
.option("--pillars <list>", "comma-separated content pillars")
|
|
36
37
|
.option("--min-queue <n>", "ideas to keep queued (1-30)")
|
|
@@ -78,6 +79,8 @@ export function registerAutopilotCommands(program, client) {
|
|
|
78
79
|
body.timezone = opts.timezone;
|
|
79
80
|
if (opts.productUrl)
|
|
80
81
|
body.product_url = opts.productUrl;
|
|
82
|
+
if (opts.mediaSource)
|
|
83
|
+
body.media_source = opts.mediaSource;
|
|
81
84
|
if (opts.productImage)
|
|
82
85
|
body.product_image_url = opts.productImage === "none" ? null : opts.productImage;
|
|
83
86
|
if (opts.brief)
|
package/dist/commands/boosts.js
CHANGED
|
@@ -8,7 +8,8 @@ export function registerBoostCommands(program, client) {
|
|
|
8
8
|
.requiredOption("-b, --daily-budget-cents <n>", "daily budget in cents (100 - 10000 = $1 - $100)")
|
|
9
9
|
.requiredOption("-d, --duration-days <n>", "campaign duration in days (1 - 7)")
|
|
10
10
|
.option("--platform <platform>", "ad platform: facebook | instagram", "facebook")
|
|
11
|
-
.option("--objective <objective>", "
|
|
11
|
+
.option("--objective <objective>", "engagement | traffic (link clicks, facebook posts only)", "engagement")
|
|
12
|
+
.option("--max-cpc-cents <n>", "pause when the average cost per click passes this many cents")
|
|
12
13
|
.option("--ad-account-id <id>", "explicit meta ad account id (e.g. 'act_123'); defaults to the first active one")
|
|
13
14
|
.action(async (opts) => {
|
|
14
15
|
try {
|
|
@@ -20,6 +21,8 @@ export function registerBoostCommands(program, client) {
|
|
|
20
21
|
};
|
|
21
22
|
if (opts.adAccountId)
|
|
22
23
|
body.ad_account_id = opts.adAccountId;
|
|
24
|
+
if (opts.maxCpcCents)
|
|
25
|
+
body.stop_rules = { max_cpc_cents: parseInt(opts.maxCpcCents, 10) };
|
|
23
26
|
const result = await client.post(`/launches/${opts.launchId}/boost`, body);
|
|
24
27
|
formatOutput(result, getFormat(program.opts()));
|
|
25
28
|
}
|
package/dist/commands/media.js
CHANGED
|
@@ -60,4 +60,65 @@ export function registerMediaCommands(program, client) {
|
|
|
60
60
|
handleError(error);
|
|
61
61
|
}
|
|
62
62
|
});
|
|
63
|
+
const library = media.command("library").description("your own photos and videos for autopilot to post");
|
|
64
|
+
library
|
|
65
|
+
.command("list")
|
|
66
|
+
.description("list media library items")
|
|
67
|
+
.option("-p, --page <n>", "page number", "1")
|
|
68
|
+
.option("-s, --page-size <n>", "items per page (max 100)", "50")
|
|
69
|
+
.action(async (opts) => {
|
|
70
|
+
try {
|
|
71
|
+
const result = await client.get("/media-library", { page: opts.page, page_size: opts.pageSize });
|
|
72
|
+
formatOutput(result, getFormat(program.opts()));
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
handleError(error);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
library
|
|
79
|
+
.command("add <url>")
|
|
80
|
+
.description("add a photo or video by public URL")
|
|
81
|
+
.requiredOption("-t, --type <type>", "image or video")
|
|
82
|
+
.option("-d, --description <text>", "what it shows (autopilot chooses media by this)")
|
|
83
|
+
.option("--thumbnail <url>", "video thumbnail URL")
|
|
84
|
+
.action(async (url, opts) => {
|
|
85
|
+
try {
|
|
86
|
+
const item = { url, type: opts.type };
|
|
87
|
+
if (opts.description)
|
|
88
|
+
item.description = opts.description;
|
|
89
|
+
if (opts.thumbnail)
|
|
90
|
+
item.thumbnail_url = opts.thumbnail;
|
|
91
|
+
const result = await client.post("/media-library", { items: [item] });
|
|
92
|
+
formatOutput(result, getFormat(program.opts()));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
handleError(error);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
library
|
|
99
|
+
.command("describe <id> <description>")
|
|
100
|
+
.description("set what a library item shows (\"none\" clears it)")
|
|
101
|
+
.action(async (id, description) => {
|
|
102
|
+
try {
|
|
103
|
+
const result = await client.patch(`/media-library/${id}`, {
|
|
104
|
+
description: description === "none" ? null : description,
|
|
105
|
+
});
|
|
106
|
+
formatOutput(result, getFormat(program.opts()));
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
handleError(error);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
library
|
|
113
|
+
.command("delete <id>")
|
|
114
|
+
.description("remove an item from the library")
|
|
115
|
+
.action(async (id) => {
|
|
116
|
+
try {
|
|
117
|
+
await client.delete(`/media-library/${id}`);
|
|
118
|
+
formatOutput({ id, deleted: true }, getFormat(program.opts()));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
handleError(error);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
63
124
|
}
|
package/dist/tool-policy.js
CHANGED
|
@@ -74,6 +74,13 @@ export const TOOL_POLICY = {
|
|
|
74
74
|
// media
|
|
75
75
|
generate_media: hosted("media", "write"),
|
|
76
76
|
get_media_job: hosted("media", "read"),
|
|
77
|
+
// stdio only for now: the hosted set is pinned to what was submitted to Meta on 10/13/2026
|
|
78
|
+
// (__tests__/hosted.test.ts). Move these to hosted() once that review settles.
|
|
79
|
+
list_media_library: localOnly("media", "read"),
|
|
80
|
+
add_to_media_library: localOnly("media", "write"),
|
|
81
|
+
update_media_library_item: localOnly("media", "write"),
|
|
82
|
+
// removing an item changes what autopilot posts, but nothing published is touched
|
|
83
|
+
delete_media_library_item: localOnly("media", "write"),
|
|
77
84
|
// connections
|
|
78
85
|
list_connections: hosted("connections", "read"),
|
|
79
86
|
// edits the profile ON the platform (youtube channel description)
|
package/dist/tools/autopilot.js
CHANGED
|
@@ -42,6 +42,10 @@ export function registerAutopilotTools(server, client) {
|
|
|
42
42
|
run_hour: z.number().int().min(0).max(23).optional().describe("local hour the nightly run fires"),
|
|
43
43
|
timezone: z.string().optional().describe("IANA timezone, e.g. America/New_York"),
|
|
44
44
|
product_url: z.string().nullable().optional().describe("product link included in posts"),
|
|
45
|
+
media_source: z
|
|
46
|
+
.enum(["ai", "library", "mixed"])
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("where image and video posts get media: ai (generated), library (only the user's media library), mixed (library first, AI when nothing fits)"),
|
|
45
49
|
product_image_url: z
|
|
46
50
|
.string()
|
|
47
51
|
.nullable()
|
package/dist/tools/boosts.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Launch boost MCP tools — paid amplification of organic Facebook launches.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Engagement or traffic objective, optional cost-per-click stop rule, auto-targeting. API key auth.
|
|
5
5
|
*/
|
|
6
6
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import { DropspaceClient } from "../client.js";
|
package/dist/tools/boosts.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Launch boost MCP tools — paid amplification of organic Facebook launches.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Engagement or traffic objective, optional cost-per-click stop rule, auto-targeting. API key auth.
|
|
5
5
|
*/
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
const platformSchema = z
|
|
8
8
|
.enum(["facebook", "instagram"])
|
|
9
9
|
.describe("ad platform — facebook or instagram (IG boost requires the IG business account linked to one of the user's facebook pages)");
|
|
10
|
-
const objectiveSchema = z
|
|
10
|
+
const objectiveSchema = z
|
|
11
|
+
.enum(["engagement", "traffic"])
|
|
12
|
+
.describe("engagement optimizes for reactions on the post; traffic optimizes for clicks on the link of a facebook link post (no photos or video)");
|
|
11
13
|
const launchIdParam = z.string().uuid().describe("launch ID to boost — must have a successful facebook post");
|
|
12
14
|
const boostIdParam = z.string().uuid().describe("boost ID");
|
|
13
15
|
export function registerBoostTools(server, client) {
|
|
@@ -26,6 +28,13 @@ export function registerBoostTools(server, client) {
|
|
|
26
28
|
.string()
|
|
27
29
|
.optional()
|
|
28
30
|
.describe("explicit meta ad account id (e.g. 'act_123'). defaults to the first active ad account."),
|
|
31
|
+
max_cpc_cents: z
|
|
32
|
+
.number()
|
|
33
|
+
.int()
|
|
34
|
+
.min(10)
|
|
35
|
+
.max(10000)
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("pause the boost when its average cost per click passes this many cents. it also always pauses when click-through falls below half its first day"),
|
|
29
38
|
}, async (args) => {
|
|
30
39
|
try {
|
|
31
40
|
const result = await client.post(`/launches/${args.launch_id}/boost`, {
|
|
@@ -34,6 +43,7 @@ export function registerBoostTools(server, client) {
|
|
|
34
43
|
duration_days: args.duration_days,
|
|
35
44
|
objective: args.objective ?? "engagement",
|
|
36
45
|
...(args.ad_account_id ? { ad_account_id: args.ad_account_id } : {}),
|
|
46
|
+
...(args.max_cpc_cents ? { stop_rules: { max_cpc_cents: args.max_cpc_cents } } : {}),
|
|
37
47
|
});
|
|
38
48
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
39
49
|
}
|
package/dist/tools/media.js
CHANGED
|
@@ -84,4 +84,65 @@ export function registerMediaTools(server, client) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
|
+
const text = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
|
|
88
|
+
const fail = (error) => ({
|
|
89
|
+
content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
90
|
+
isError: true,
|
|
91
|
+
});
|
|
92
|
+
server.tool("list_media_library", "list the user's media library: their own photos and videos that autopilot can post (when autopilot's media_source is library or mixed). Each item's description is what autopilot reads to choose it.", {
|
|
93
|
+
page: z.number().int().min(1).optional().describe("page number (default 1)"),
|
|
94
|
+
page_size: z.number().int().min(1).max(100).optional().describe("items per page (default 50, max 100)"),
|
|
95
|
+
}, async (args) => {
|
|
96
|
+
try {
|
|
97
|
+
const params = {};
|
|
98
|
+
if (args.page)
|
|
99
|
+
params.page = String(args.page);
|
|
100
|
+
if (args.page_size)
|
|
101
|
+
params.page_size = String(args.page_size);
|
|
102
|
+
return text(await client.get("/media-library", params));
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return fail(error);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
server.tool("add_to_media_library", "add photos or videos to the user's media library by public URL (up to 20 at once; a URL already in the library is skipped). Give each a description of what it shows - autopilot chooses media by it; images without one are captioned automatically on autopilot's next run, videos need one.", {
|
|
109
|
+
items: z
|
|
110
|
+
.array(z.object({
|
|
111
|
+
url: z.string().url().describe("public http(s) URL of the image or video"),
|
|
112
|
+
type: z.enum(["image", "video"]),
|
|
113
|
+
description: z.string().max(500).optional().describe("what the media shows"),
|
|
114
|
+
thumbnail_url: z.string().url().optional().describe("video thumbnail (lets autopilot caption it)"),
|
|
115
|
+
mime_type: z.string().optional(),
|
|
116
|
+
filename: z.string().optional(),
|
|
117
|
+
}))
|
|
118
|
+
.min(1)
|
|
119
|
+
.max(20),
|
|
120
|
+
}, async (args) => {
|
|
121
|
+
try {
|
|
122
|
+
return text(await client.post("/media-library", args));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return fail(error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
server.tool("update_media_library_item", "set the description of a media library item (what it shows; autopilot reads it to choose media). null clears it.", {
|
|
129
|
+
id: z.string().uuid().describe("media library item ID"),
|
|
130
|
+
description: z.string().max(500).nullable(),
|
|
131
|
+
}, async (args) => {
|
|
132
|
+
try {
|
|
133
|
+
return text(await client.patch(`/media-library/${args.id}`, { description: args.description }));
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return fail(error);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
server.tool("delete_media_library_item", "remove an item from the user's media library so autopilot stops using it. Launches that already posted it keep their media.", { id: z.string().uuid().describe("media library item ID") }, async (args) => {
|
|
140
|
+
try {
|
|
141
|
+
await client.delete(`/media-library/${args.id}`);
|
|
142
|
+
return text({ id: args.id, deleted: true });
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return fail(error);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
87
148
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -325,7 +325,7 @@ export interface Boost {
|
|
|
325
325
|
platform: "facebook" | "instagram";
|
|
326
326
|
post_id: string;
|
|
327
327
|
status: "paused" | "active" | "completed" | "failed" | "canceled";
|
|
328
|
-
objective: "engagement";
|
|
328
|
+
objective: "engagement" | "traffic";
|
|
329
329
|
daily_budget_cents: number;
|
|
330
330
|
duration_days: number;
|
|
331
331
|
meta_ad_account_id: string;
|