@jclvsh/dropspace 2.42.0 → 2.48.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
@@ -377,6 +377,22 @@ Reddit engagement (drafted comments in other communities) is paused: its tools a
377
377
  | `get_audience_demographics` | Age, gender, country, and city of your Instagram followers and engaged audience (local package only, not the hosted endpoint) |
378
378
  | `get_reels_read` | Each Instagram account's latest reels read (local package only, not the hosted endpoint) |
379
379
  | `generate_reels_read` | Claude compares your top, middle, and bottom reels of the last 90 days; pro and premium, one a day per account (local package only, not the hosted endpoint) |
380
+ | `list_trial_reels` | Trial reel programs: each Instagram account's program, posting hours, and reshared reels with their 1/3/7-day numbers; pro and premium (local package only) |
381
+ | `update_trial_reel_program` | Turn an account's trial reel program on or off, or change reels per day, auto-approve, skip terms, timezone (send scope to turn on; local package only) |
382
+ | `scan_trial_reels` | Scan an account's reels again and queue the strong ones (local package only) |
383
+ | `update_trial_reel` | Approve, hold, re-caption, or remove a waiting reel, or approve all (send scope to approve; local package only) |
384
+
385
+ ### Reports and workflows
386
+
387
+ | Tool | Description |
388
+ | ---- | ----------- |
389
+ | `save_report` | Save a markdown report to the user's reports page (local package only) |
390
+ | `list_reports` | Saved reports, newest first, without bodies (local package only) |
391
+ | `get_report` | One report with its markdown body (local package only) |
392
+
393
+ Prompts (local package only): `instagram_audit`, `best_times_plan`, `winners_report`. Each tells the AI app which tools to call and ends with `save_report`.
394
+
395
+ CLI: `dropspace reports list|get|delete`.
380
396
 
381
397
  #### `get_audience` Parameters
382
398
 
@@ -385,7 +401,7 @@ Reddit engagement (drafted comments in other communities) is paused: its tools a
385
401
  | `window_days` | number | No | Growth window in days (1-365, default 30) |
386
402
  | `include_instagram_insights` | boolean | No | Add Instagram growth analytics to each Instagram entry (default false) |
387
403
 
388
- CLI: `dropspace audience [-w, --window-days <n>] [--instagram-insights]`, `dropspace trial-reels`, `dropspace best-hours [--timezone <zone>]`, `dropspace demographics [--timeframe <this_week|this_month|last_90_days>]`, and `dropspace reels-read [--generate] [--connection-id <id>]`.
404
+ CLI: `dropspace audience [-w, --window-days <n>] [--instagram-insights]`, `dropspace trial-reels`, `dropspace best-hours [--timezone <zone>]`, `dropspace demographics [--timeframe <this_week|this_month|last_90_days>]`, `dropspace reels-read [--generate] [--connection-id <id>]`, and `dropspace trial-reel-program list|set|scan|approve|approve-all|remove`.
389
405
 
390
406
  With `include_instagram_insights`, each Instagram entry carries `instagram_insights`: follows, unfollows, net, and reach split into followers and non-followers per Pacific-time day, an estimated followers-over-time series rebuilt back from the current count, weekly growth velocity, the share of reach from non-followers, and contact-button taps. Instagram revises the last 7 days (`settled: false`). Each call reads up to 31 days, newest first, so a 90-day window fills over a few calls (`days_pending`). Instagram reports none of this for accounts under 100 followers (`available: false`).
391
407
 
@@ -39,4 +39,46 @@ export function registerAccountCommands(program, client) {
39
39
  handleError(error);
40
40
  }
41
41
  });
42
+ const reports = program.command("reports").description("your saved reports (from the MCP workflows or the API)");
43
+ reports
44
+ .command("list")
45
+ .description("saved reports, newest first, without their bodies")
46
+ .option("--page <n>", "page number")
47
+ .option("--page-size <n>", "reports per page (max 100)")
48
+ .action(async (options) => {
49
+ try {
50
+ const params = {};
51
+ if (options.page)
52
+ params.page = options.page;
53
+ if (options.pageSize)
54
+ params.page_size = options.pageSize;
55
+ formatOutput(await client.get("/reports", params), getFormat(program.opts()));
56
+ }
57
+ catch (error) {
58
+ handleError(error);
59
+ }
60
+ });
61
+ reports
62
+ .command("get <id>")
63
+ .description("one report with its markdown body")
64
+ .action(async (id) => {
65
+ try {
66
+ formatOutput(await client.get(`/reports/${id}`), getFormat(program.opts()));
67
+ }
68
+ catch (error) {
69
+ handleError(error);
70
+ }
71
+ });
72
+ reports
73
+ .command("delete <id>")
74
+ .description("delete a saved report")
75
+ .action(async (id) => {
76
+ try {
77
+ await client.delete(`/reports/${id}`);
78
+ formatOutput({ data: { id, deleted: true } }, getFormat(program.opts()));
79
+ }
80
+ catch (error) {
81
+ handleError(error);
82
+ }
83
+ });
42
84
  }
@@ -108,4 +108,96 @@ export function registerAudienceCommands(program, client) {
108
108
  }
109
109
  });
110
110
  }
111
+ const trial = program
112
+ .command("trial-reel-program")
113
+ .description("reshare your strongest instagram reels as trial reels (pro and premium)");
114
+ trial
115
+ .command("list")
116
+ .description("each instagram account with its trial reel program, posting hours, and reels")
117
+ .action(async () => {
118
+ try {
119
+ formatOutput(await client.get("/trial-reels"), getFormat(program.opts()));
120
+ }
121
+ catch (error) {
122
+ handleError(error);
123
+ }
124
+ });
125
+ trial
126
+ .command("set <connectionId>")
127
+ .description("change a program; --on needs the send scope and scans the account's reels the first time")
128
+ .option("--on", "turn the program on")
129
+ .option("--off", "turn the program off")
130
+ .option("--per-day <n>", "trial reels a day, 1-3")
131
+ .option("--auto-approve <bool>", "post queued reels without approving each one (true or false)")
132
+ .option("--skip <terms>", "comma-separated: never queue a reel whose caption contains one")
133
+ .option("--timezone <tz>", "IANA timezone for posting hours")
134
+ .action(async (connectionId, options) => {
135
+ try {
136
+ const body = {
137
+ ...(options.on ? { enabled: true } : options.off ? { enabled: false } : {}),
138
+ ...(options.perDay ? { reels_per_day: Number(options.perDay) } : {}),
139
+ ...(options.autoApprove !== undefined
140
+ ? { auto_approve: ["true", "yes", "on", "1"].includes(options.autoApprove.toLowerCase()) }
141
+ : {}),
142
+ ...(options.skip !== undefined
143
+ ? {
144
+ skip_caption_terms: options.skip
145
+ .split(",")
146
+ .map((term) => term.trim())
147
+ .filter(Boolean),
148
+ }
149
+ : {}),
150
+ ...(options.timezone ? { timezone: options.timezone } : {}),
151
+ };
152
+ formatOutput(await client.put(`/trial-reels/programs/${connectionId}`, body), getFormat(program.opts()));
153
+ }
154
+ catch (error) {
155
+ handleError(error);
156
+ }
157
+ });
158
+ trial
159
+ .command("scan <connectionId>")
160
+ .description("scan the account's reels again and queue the strong ones")
161
+ .action(async (connectionId) => {
162
+ try {
163
+ formatOutput(await client.post(`/trial-reels/programs/${connectionId}/scan`), getFormat(program.opts()));
164
+ }
165
+ catch (error) {
166
+ handleError(error);
167
+ }
168
+ });
169
+ trial
170
+ .command("approve <itemId>")
171
+ .description("approve a waiting reel so it posts at the next posting hour (send scope)")
172
+ .action(async (itemId) => {
173
+ try {
174
+ formatOutput(await client.patch(`/trial-reels/items/${itemId}`, { approved: true }), getFormat(program.opts()));
175
+ }
176
+ catch (error) {
177
+ handleError(error);
178
+ }
179
+ });
180
+ trial
181
+ .command("approve-all <connectionId>")
182
+ .description("approve every waiting reel with a caption (send scope)")
183
+ .action(async (connectionId) => {
184
+ try {
185
+ formatOutput(await client.post(`/trial-reels/programs/${connectionId}/approve-all`), getFormat(program.opts()));
186
+ }
187
+ catch (error) {
188
+ handleError(error);
189
+ }
190
+ });
191
+ trial
192
+ .command("remove <itemId>")
193
+ .description("remove a waiting reel for good")
194
+ .action(async (itemId) => {
195
+ try {
196
+ await client.delete(`/trial-reels/items/${itemId}`);
197
+ formatOutput({ data: { id: itemId, status: "removed" } }, getFormat(program.opts()));
198
+ }
199
+ catch (error) {
200
+ handleError(error);
201
+ }
202
+ });
111
203
  }
package/dist/server.js CHANGED
@@ -17,6 +17,8 @@ import { registerRedditTools } from "./tools/reddit.js";
17
17
  import { registerAudienceTools } from "./tools/audience.js";
18
18
  import { registerCommentTools } from "./tools/comments.js";
19
19
  import { registerAccountTools } from "./tools/account.js";
20
+ import { registerTrialReelTools } from "./tools/trial-reels.js";
21
+ import { registerReportTools, registerWorkflowPrompts } from "./tools/reports.js";
20
22
  /** Drops tools whose feature is paused before the policy registrar sees them (see PAUSED_TOOLS). */
21
23
  function skipPausedTools(registrar) {
22
24
  return {
@@ -46,5 +48,11 @@ export function createDropspaceServer({ client, version, profile }) {
46
48
  registerAudienceTools(registrar, client);
47
49
  registerCommentTools(registrar, client);
48
50
  registerAccountTools(registrar, client);
51
+ registerTrialReelTools(registrar, client);
52
+ registerReportTools(registrar, client);
53
+ // Prompts are not tools, so the policy registrar does not filter them: the hosted profile
54
+ // (frozen for the Meta review) gets none.
55
+ if (profile === "full")
56
+ registerWorkflowPrompts(server);
49
57
  return server;
50
58
  }
@@ -128,6 +128,14 @@ const FULL_TOOL_POLICY = {
128
128
  get_reels_read: localOnly("audience", "read"),
129
129
  // spends a Claude call and up to 60 Instagram requests; changes nothing the user posted
130
130
  generate_reels_read: localOnly("audience", "write"),
131
+ list_trial_reels: localOnly("audience", "read"),
132
+ // turning a program on or approving reels leads to instagram posts
133
+ update_trial_reel_program: localOnly("audience", "sensitive"),
134
+ scan_trial_reels: localOnly("audience", "write"),
135
+ update_trial_reel: localOnly("audience", "sensitive"),
136
+ save_report: localOnly("account", "write"),
137
+ list_reports: localOnly("account", "read"),
138
+ get_report: localOnly("account", "read"),
131
139
  get_usage: hosted("usage", "read"),
132
140
  get_changelog: hosted("usage", "read"),
133
141
  get_dropspace_status: hosted("dropspace", "read"),
@@ -76,7 +76,7 @@ export function registerCommentTools(server, client) {
76
76
  return fail(error);
77
77
  }
78
78
  });
79
- server.tool("list_comment_queue", "every comment still waiting for you across your instagram and youtube posts made through dropspace in the last 30 days (newest 30 posts, newest 50 comments each), newest first, up to 200. waiting: not hidden, not written or answered by your account, and not replied to, hidden, deleted, or dismissed through dropspace. each comment carries its post (platform, post_id, launch_id, launch_name) and claude's read: kind (question, feedback, praise, spam, attack, other; null until sorted), suggested_action (reply, hide, dismiss), and reply_draft for questions and feedback; may_be_answered is true when youtube returned only some replies and none was yours. counts per kind; post_issues lists posts that could not be read. reply with reply_to_comment, delete with delete_comment (launch_id + post_id + comment id); hide or dismiss several with act_on_comments.", {}, async () => {
79
+ server.tool("list_comment_queue", "every comment still waiting for you across your instagram and youtube posts made through dropspace in the last 30 days (up to 30 posts, the ones with comments first; newest 50 comments each; posts_in_window says how many posts the window has), newest first, up to 200. waiting: not hidden, not written or answered by your account, and not replied to, hidden, deleted, or dismissed through dropspace. each comment carries its post (platform, post_id, launch_id, launch_name) and claude's read: kind (question, feedback, praise, spam, attack, other; null until sorted), suggested_action (reply, hide, dismiss), and reply_draft for questions and feedback; may_be_answered is true when youtube returned only some replies and none was yours. counts per kind; post_issues lists posts that could not be read. reply with reply_to_comment, delete with delete_comment (launch_id + post_id + comment id); hide or dismiss several with act_on_comments.", {}, async () => {
80
80
  try {
81
81
  return text(await client.get("/comments/queue"));
82
82
  }
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { DropspaceClient } from "../client.js";
3
+ /** The workflow prompts (Instagram audit, best-times plan, winners report). Local package only. */
4
+ export declare function registerWorkflowPrompts(server: McpServer): void;
5
+ export declare function registerReportTools(server: McpServer, client: DropspaceClient): void;
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import { WORKFLOWS } from "../workflows.js";
3
+ function text(result) {
4
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5
+ }
6
+ function fail(error) {
7
+ return {
8
+ content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
9
+ isError: true,
10
+ };
11
+ }
12
+ /** The workflow prompts (Instagram audit, best-times plan, winners report). Local package only. */
13
+ export function registerWorkflowPrompts(server) {
14
+ for (const workflow of WORKFLOWS) {
15
+ server.registerPrompt(workflow.name, { title: workflow.title, description: workflow.description }, () => ({
16
+ description: workflow.description,
17
+ messages: [{ role: "user", content: { type: "text", text: workflow.prompt } }],
18
+ }));
19
+ }
20
+ }
21
+ export function registerReportTools(server, client) {
22
+ server.tool("save_report", "save a markdown report to the user's dropspace reports (the reports page in the app): the end step of the instagram_audit, best_times_plan, and winners_report workflows. title up to 200 characters, body up to 100,000. at most 200 saved reports; past that the save is refused and the user deletes some first. returns the saved report with its id.", {
23
+ title: z.string().min(1).max(200).describe("report title, e.g. 'Instagram audit - 10/29/2026'"),
24
+ body: z.string().min(1).max(100_000).describe("the report in markdown"),
25
+ kind: z.enum(["audit", "best_times", "winners", "other"]).optional().describe("which workflow made it"),
26
+ }, async ({ title, body, kind }) => {
27
+ try {
28
+ return text(await client.post("/reports", {
29
+ title,
30
+ body,
31
+ kind: kind ?? "other",
32
+ source: "mcp",
33
+ }));
34
+ }
35
+ catch (error) {
36
+ return fail(error);
37
+ }
38
+ });
39
+ server.tool("list_reports", "the user's saved reports, newest first, without their bodies: id, title, kind (audit, best_times, winners, other), source (mcp or api), created_at. read one with get_report.", {
40
+ page: z.number().int().min(1).optional(),
41
+ page_size: z.number().int().min(1).max(100).optional(),
42
+ }, async ({ page, page_size }) => {
43
+ try {
44
+ const params = {};
45
+ if (page)
46
+ params.page = String(page);
47
+ if (page_size)
48
+ params.page_size = String(page_size);
49
+ return text(await client.get("/reports", params));
50
+ }
51
+ catch (error) {
52
+ return fail(error);
53
+ }
54
+ });
55
+ server.tool("get_report", "one saved report with its markdown body.", { id: z.string().uuid().describe("report id from list_reports or save_report") }, async ({ id }) => {
56
+ try {
57
+ return text(await client.get(`/reports/${id}`));
58
+ }
59
+ catch (error) {
60
+ return fail(error);
61
+ }
62
+ });
63
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { DropspaceClient } from "../client.js";
3
+ export declare function registerTrialReelTools(server: McpServer, client: DropspaceClient): void;
@@ -0,0 +1,87 @@
1
+ import { z } from "zod";
2
+ function text(result) {
3
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4
+ }
5
+ function fail(error) {
6
+ return {
7
+ content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
8
+ isError: true,
9
+ };
10
+ }
11
+ export function registerTrialReelTools(server, client) {
12
+ server.tool("list_trial_reels", "trial reel programs (pro and premium): each connected instagram account with eligible (instagram takes trial reels only from professional accounts with 200+ followers), its program (absent until saved: enabled, auto_approve, reels_per_day 1-3, skip_caption_terms, timezone, last_note), posting_hours (0-23 in the program's timezone: the account's best hours, else 9, 13, 18), and items, newest 100: status queued (waits for approval), posting, posted, won, retired, or failed (failure says why), with reason, caption, the source reel and its views, win_target (half the source's views, capped at 10,000), and checkpoints d1/d3/d7.", {}, async () => {
13
+ try {
14
+ return text(await client.get("/trial-reels"));
15
+ }
16
+ catch (error) {
17
+ return fail(error);
18
+ }
19
+ });
20
+ server.tool("update_trial_reel_program", "create or change the trial reel program for one instagram account. turning it on the first time scans the account's reels and queues the strong ones (returned as scan). nothing posts until a reel is approved (update_trial_reel, or auto_approve). approved reels are reshared as instagram trial reels (shown to non-followers only, graduation manual) one per posting hour, never within 2 hours of another dropspace post to that account; each is a launch on the user's plan. enabled or auto_approve true needs the send scope.", {
21
+ connection_id: z.string().uuid().describe("instagram connection_id from list_trial_reels"),
22
+ enabled: z.boolean().optional().describe("turn the program on or off"),
23
+ auto_approve: z.boolean().optional().describe("post queued reels without approving each one"),
24
+ reels_per_day: z.number().int().min(1).max(3).optional().describe("trial reels a day, 1-3"),
25
+ skip_caption_terms: z
26
+ .array(z.string().max(50))
27
+ .max(20)
28
+ .optional()
29
+ .describe("never queue a reel whose caption contains one of these"),
30
+ timezone: z.string().optional().describe("IANA timezone for posting hours, e.g. America/New_York"),
31
+ edit_kinds: z
32
+ .array(z.enum(["reshare", "background", "subtitles", "hook", "sticker"]))
33
+ .min(1)
34
+ .optional()
35
+ .describe("kinds new reels take in turn: reshare (the original video), background (on a colored background), subtitles (burned-in; only reels with speech and no captions on screen), hook (a strong line from later in the reel played first), or sticker (a reply-to-comment card with a comment claude writes)"),
36
+ }, async ({ connection_id, ...settings }) => {
37
+ try {
38
+ return text(await client.put(`/trial-reels/programs/${connection_id}`, settings));
39
+ }
40
+ catch (error) {
41
+ return fail(error);
42
+ }
43
+ });
44
+ server.tool("scan_trial_reels", "scan an instagram account's reels again (last 180 days, up to 100) and queue the ones with 1.5x the median views or more that are older than 21 days and were never queued. returns queued, rated, median_views, and note (why nothing was queued). one scan per account every 10 minutes.", { connection_id: z.string().uuid().describe("instagram connection_id with a saved program") }, async ({ connection_id }) => {
45
+ try {
46
+ return text(await client.post(`/trial-reels/programs/${connection_id}/scan`));
47
+ }
48
+ catch (error) {
49
+ return fail(error);
50
+ }
51
+ });
52
+ server.tool("update_trial_reel", "change one waiting (queued) trial reel: approved true lets it post at the next posting hour (send scope; needs a caption), false holds it; caption replaces the caption it posts with (up to 2,200 characters) and holds the reel unless approved true is sent with it; remove true takes it out of the queue for good. approve every waiting reel at once with approve_all.", {
53
+ id: z.string().uuid().optional().describe("item id from list_trial_reels (not needed with approve_all)"),
54
+ approved: z.boolean().optional(),
55
+ caption: z.string().max(2200).optional(),
56
+ kind: z
57
+ .enum(["reshare", "background", "subtitles", "hook", "sticker"])
58
+ .optional()
59
+ .describe("post the original (reshare) or an edited version (background, subtitles, hook, sticker); a new kind holds the reel"),
60
+ remove: z.boolean().optional(),
61
+ approve_all: z
62
+ .string()
63
+ .uuid()
64
+ .optional()
65
+ .describe("instead of one item: a connection_id whose waiting reels with captions are all approved"),
66
+ }, async ({ id, approved, caption, kind, remove, approve_all }) => {
67
+ try {
68
+ if (approve_all) {
69
+ return text(await client.post(`/trial-reels/programs/${approve_all}/approve-all`));
70
+ }
71
+ if (!id)
72
+ return fail(new Error("send id, or approve_all with a connection_id"));
73
+ if (remove) {
74
+ await client.delete(`/trial-reels/items/${id}`);
75
+ return text({ data: { id, status: "removed" } });
76
+ }
77
+ return text(await client.patch(`/trial-reels/items/${id}`, {
78
+ ...(approved !== undefined ? { approved } : {}),
79
+ ...(caption !== undefined ? { caption } : {}),
80
+ ...(kind !== undefined ? { kind } : {}),
81
+ }));
82
+ }
83
+ catch (error) {
84
+ return fail(error);
85
+ }
86
+ });
87
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Workflow prompts: ready-made instructions an AI app (Claude, ChatGPT, any MCP client) runs
3
+ * against the dropspace MCP tools, ending with save_report. The MCP server serves them as MCP
4
+ * prompts, and the docs page imports this file (@jclvsh/dropspace/workflows) to show the same text.
5
+ */
6
+ export interface Workflow {
7
+ /** MCP prompt name. */
8
+ name: string;
9
+ title: string;
10
+ description: string;
11
+ /** The report kind save_report is asked to use. */
12
+ kind: "audit" | "best_times" | "winners";
13
+ prompt: string;
14
+ }
15
+ export declare const WORKFLOWS: Workflow[];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Workflow prompts: ready-made instructions an AI app (Claude, ChatGPT, any MCP client) runs
3
+ * against the dropspace MCP tools, ending with save_report. The MCP server serves them as MCP
4
+ * prompts, and the docs page imports this file (@jclvsh/dropspace/workflows) to show the same text.
5
+ */
6
+ export const WORKFLOWS = [
7
+ {
8
+ name: "instagram_audit",
9
+ title: "Instagram audit",
10
+ description: "where the account stands: growth, who it reaches, what its reels have in common, what to fix first",
11
+ kind: "audit",
12
+ prompt: [
13
+ "Audit my Instagram account using the dropspace tools.",
14
+ "1. Call get_audience with include_instagram_insights true and window_days 30: followers, net growth per day, follows vs unfollows, and the share of reach from non-followers.",
15
+ "2. Call get_audience_demographics: who follows and who engages (age, gender, top countries and cities).",
16
+ "3. Call get_reels_read; if there is no read from the last 7 days, call generate_reels_read. Use its findings on what the strong and weak reels share.",
17
+ "4. Call get_best_posting_hours for when followers are online.",
18
+ "Write the audit in markdown with these sections: Where the account stands (numbers, with dates), Who it reaches, What works, What doesn't, The three changes to make this week. Quote numbers exactly as the tools returned them and say when a tool had no data.",
19
+ 'Then call save_report with kind "audit" and a title like "Instagram audit - <today\'s date>".',
20
+ ].join("\n"),
21
+ },
22
+ {
23
+ name: "best_times_plan",
24
+ title: "Best-times plan",
25
+ description: "a weekly posting schedule built from when the followers are online",
26
+ kind: "best_times",
27
+ prompt: [
28
+ "Build my weekly Instagram posting plan using the dropspace tools.",
29
+ "1. Call get_best_posting_hours with my timezone: the hourly averages, the weekday-by-hour grid, and the busiest hours.",
30
+ "2. Call get_autopilot to see the posting times autopilot uses now, if it is set up.",
31
+ "3. Call list_launches for the last 30 days to see when I actually posted.",
32
+ "Write the plan in markdown: a table of days and times (at most 2 a day, at least 2 hours apart), why each time was picked (followers online at that hour), and what to change from the current schedule. Say plainly if Instagram had no online-followers numbers (accounts under 100 followers) and fall back to 9 am, 1 pm, and 6 pm.",
33
+ 'Then call save_report with kind "best_times" and a title like "Posting plan - week of <date>".',
34
+ ].join("\n"),
35
+ },
36
+ {
37
+ name: "winners_report",
38
+ title: "Winners report",
39
+ description: "the posts and trial reels that won lately, what they share, and what to make next",
40
+ kind: "winners",
41
+ prompt: [
42
+ "Find my recent winners using the dropspace tools.",
43
+ "1. Call list_launches for the last 30 days, then get_launch_analytics on the posted ones to get views, likes, comments, shares, and saves.",
44
+ "2. Call get_trial_reel_analytics and list_trial_reels for trial reels and their 1, 3, and 7-day results (won or retired).",
45
+ "3. Call get_reels_read for the latest read of the account's reels.",
46
+ "Rank posts by views relative to the account's usual (the median of the posts you read). Write the report in markdown: the top five with their numbers and links, what they share (topic, hook, format, length, posting time), the bottom three and what they share, and three posts to make next. Use only the numbers the tools returned.",
47
+ 'Then call save_report with kind "winners" and a title like "Winners - <month>".',
48
+ ].join("\n"),
49
+ },
50
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jclvsh/dropspace",
3
- "version": "2.42.0",
3
+ "version": "2.48.0",
4
4
  "description": "MCP server and CLI for the Dropspace API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,6 +27,10 @@
27
27
  "types": "./dist/tool-policy.d.ts",
28
28
  "default": "./dist/tool-policy.js"
29
29
  },
30
+ "./workflows": {
31
+ "types": "./dist/workflows.d.ts",
32
+ "default": "./dist/workflows.js"
33
+ },
30
34
  "./package.json": "./package.json"
31
35
  },
32
36
  "bin": {