@jclvsh/dropspace 2.30.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.
@@ -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)
@@ -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
  }
@@ -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)
@@ -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()
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jclvsh/dropspace",
3
- "version": "2.30.0",
3
+ "version": "2.31.0",
4
4
  "description": "MCP server and CLI for the Dropspace API",
5
5
  "repository": {
6
6
  "type": "git",